diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3fb516a3..c78ea560 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## 4.0.0 under development
- Chg #221: Throw `LogicException` in `Tag::id()` when id is empty string (@razvbir)
+- Chg #234: Remove tag attributes sorting (@FrankiFixx)
## 3.13.0 March 13, 2026
diff --git a/UPGRADE.md b/UPGRADE.md
index 37b8a9f6..c9d749e0 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -9,5 +9,9 @@ application when you upgrade the package from one version to another.
## Upgrade from 3.x
-- `Tag::id()` now throws `LogicException` when an empty string is passed. Check your code for places where you call
+- `Tag::id()` now throws `LogicException` when an empty string is passed. Check your code for places where you call
`Tag::id()` and make sure you are not passing an empty string.
+- HTML tag attributes are no longer sorted by `Html::renderTagAttributes()`. Previously, attributes were reordered
+ according to a predefined priority list (`type`, `id`, `class`, `name`, `value`, etc.). Now attributes are rendered
+ in the order they are set. If your code or tests depend on a specific attribute order in the rendered HTML, you need
+ to update them.
diff --git a/src/Html.php b/src/Html.php
index a1393ae7..d7113563 100644
--- a/src/Html.php
+++ b/src/Html.php
@@ -105,45 +105,6 @@
*/
final class Html
{
- /**
- * The preferred order of attributes in a tag. This mainly affects the order of the attributes that are
- * rendered by {@see renderTagAttributes()}.
- */
- private const ATTRIBUTE_ORDER = [
- 'type',
- 'id',
- 'class',
- 'name',
- 'value',
-
- 'href',
- 'loading',
- 'src',
- 'srcset',
- 'form',
- 'action',
- 'method',
-
- 'selected',
- 'checked',
- 'readonly',
- 'disabled',
- 'multiple',
-
- 'size',
- 'maxlength',
- 'minlength',
- 'width',
- 'height',
- 'rows',
- 'cols',
-
- 'alt',
- 'title',
- 'rel',
- 'media',
- ];
-
/**
* List of tag attributes that should be specially handled when their values are of array type.
* In particular, if the value of the `data` attribute is `['name' => 'xyz', 'age' => 13]`, two attributes will be
@@ -1680,16 +1641,6 @@ public static function address(string|Stringable $content = '', array $attribute
*/
public static function renderTagAttributes(array $attributes): string
{
- if (count($attributes) > 1) {
- $sorted = [];
- foreach (self::ATTRIBUTE_ORDER as $name) {
- if (isset($attributes[$name])) {
- $sorted[$name] = $attributes[$name];
- }
- }
- $attributes = array_merge($sorted, $attributes);
- }
-
$html = '';
/**
* @var string $name
diff --git a/tests/HtmlTest.php b/tests/HtmlTest.php
index b1256397..f13ad105 100644
--- a/tests/HtmlTest.php
+++ b/tests/HtmlTest.php
@@ -137,7 +137,7 @@ public function testMeta(): void
{
$this->assertSame('', Html::meta()->render());
$this->assertSame(
- '',
+ '',
Html::meta(['name' => 'keywords', 'content' => 'yii', 'id' => 'main'])->render(),
);
}
@@ -156,15 +156,15 @@ public function testLink(): void
public function testCssFile(): void
{
$this->assertSame(
- '',
+ '',
Html::cssFile('http://example.com')->render(),
);
$this->assertSame(
- '',
+ '',
Html::cssFile('')->render(),
);
$this->assertSame(
- '',
+ '',
Html::cssFile('http://example.com', ['id' => 'main'])->render(),
);
}
@@ -180,7 +180,7 @@ public function testJavaScriptFile(): void
Html::javaScriptFile('')->render(),
);
$this->assertSame(
- '',
+ '',
Html::javaScriptFile('http://example.com', ['id' => 'main'])->render(),
);
}
@@ -208,7 +208,7 @@ public function testMailto(): void
Html::mailto('contact me', 'info@example.com')->render(),
);
$this->assertSame(
- 'contact me',
+ 'contact me',
Html::mailto('contact me', 'info@example.com', ['id' => 'contact'])->render(),
);
}
@@ -387,15 +387,15 @@ public function testTextInput(): void
public function testColorInput(): void
{
$this->assertSame('', Html::color()->render());
- $this->assertSame('', Html::color('')->render());
- $this->assertSame('', Html::color(null, '')->render());
- $this->assertSame('', Html::color('test')->render());
+ $this->assertSame('', Html::color('')->render());
+ $this->assertSame('', Html::color(null, '')->render());
+ $this->assertSame('', Html::color('test')->render());
$this->assertSame(
- '',
+ '',
Html::color('test', '#ff0000')->render(),
);
$this->assertSame(
- '',
+ '',
Html::color('test', '#ff0000', ['required' => true])->render(),
);
}
@@ -411,7 +411,7 @@ public function testHiddenInput(): void
Html::hiddenInput('test', '43')->render(),
);
$this->assertSame(
- '',
+ '',
Html::hiddenInput('test', '43', ['id' => 'ABC'])->render(),
);
}
@@ -435,15 +435,15 @@ public function testPasswordInput(): void
public function testFile(): void
{
$this->assertSame('', Html::file()->render());
- $this->assertSame('', Html::file('')->render());
- $this->assertSame('', Html::file(null, '')->render());
- $this->assertSame('', Html::file('test')->render());
+ $this->assertSame('', Html::file('')->render());
+ $this->assertSame('', Html::file(null, '')->render());
+ $this->assertSame('', Html::file('test')->render());
$this->assertSame(
- '',
+ '',
Html::file('test', '43')->render(),
);
$this->assertSame(
- '',
+ '',
Html::file('test', '43', ['class' => 'photo'])->render(),
);
}
@@ -451,15 +451,15 @@ public function testFile(): void
public function testRadio(): void
{
$this->assertSame('', Html::radio()->render());
- $this->assertSame('', Html::radio('')->render());
- $this->assertSame('', Html::radio(null, '')->render());
- $this->assertSame('', Html::radio('test')->render());
+ $this->assertSame('', Html::radio('')->render());
+ $this->assertSame('', Html::radio(null, '')->render());
+ $this->assertSame('', Html::radio('test')->render());
$this->assertSame(
- '',
+ '',
Html::radio('test', '43')->render(),
);
$this->assertSame(
- '',
+ '',
Html::radio('test', '43', ['readonly' => true])->render(),
);
}
@@ -467,15 +467,15 @@ public function testRadio(): void
public function testCheckbox(): void
{
$this->assertSame('', Html::checkbox()->render());
- $this->assertSame('', Html::checkbox('')->render());
- $this->assertSame('', Html::checkbox(null, '')->render());
- $this->assertSame('', Html::checkbox('test')->render());
+ $this->assertSame('', Html::checkbox('')->render());
+ $this->assertSame('', Html::checkbox(null, '')->render());
+ $this->assertSame('', Html::checkbox('test')->render());
$this->assertSame(
- '',
+ '',
Html::checkbox('test', '43')->render(),
);
$this->assertSame(
- '',
+ '',
Html::checkbox('test', '43', ['readonly' => true])->render(),
);
}
@@ -483,15 +483,15 @@ public function testCheckbox(): void
public function testRange(): void
{
$this->assertSame('', Html::range()->render());
- $this->assertSame('', Html::range('')->render());
- $this->assertSame('', Html::range(null, '')->render());
- $this->assertSame('', Html::range('test')->render());
+ $this->assertSame('', Html::range('')->render());
+ $this->assertSame('', Html::range(null, '')->render());
+ $this->assertSame('', Html::range('test')->render());
$this->assertSame(
- '',
+ '',
Html::range('test', '43')->render(),
);
$this->assertSame(
- '',
+ '',
Html::range('test', '43', ['readonly' => true])->render(),
);
}
@@ -534,9 +534,9 @@ public function testCheckboxList(): void
$this->assertSame(
'' . "\n"
. '
' . "\n"
- . '' . "\n"
- . '' . "\n"
- . '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
. '
',
Html::checkboxList('test')
->items([1 => 'One', 2 => 'Two', 5 => 'Five'])
@@ -552,9 +552,9 @@ public function testRadioList(): void
$this->assertSame(
'' . "\n"
. '' . "\n"
- . '' . "\n"
- . '' . "\n"
- . '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
. '
',
Html::radioList('test')
->items([1 => 'One', 2 => 'Two', 5 => 'Five'])
@@ -708,7 +708,7 @@ public function testLi(): void
$this->assertSame('Hello', Html::li(Html::span('Hello'))->render());
$this->assertSame(
- 'Content',
+ 'Content',
Html::li('Content', ['class' => 'item', 'id' => 'item-1'])->render(),
);
$this->assertSame('', Html::li(attributes: ['class' => 'empty'])->render());
@@ -859,7 +859,7 @@ public static function dataRenderTagAttributes(): array
[' class="first second"', ['class' => ['first', 'second']]],
['', ['class' => []]],
[' style="width: 100px; height: 200px;"', ['style' => ['width' => '100px', 'height' => '200px']]],
- [' name="position" value="42"', ['value' => 42, 'name' => 'position']],
+ [' value="42" name="position"', ['value' => 42, 'name' => 'position']],
[
' id="x" class="a b" data-a="1" data-b="2" style="width: 100px;" any=\'[1,2]\'',
[
diff --git a/tests/Tag/Base/BooleanInputTagTest.php b/tests/Tag/Base/BooleanInputTagTest.php
index a1d8f27f..981143dd 100644
--- a/tests/Tag/Base/BooleanInputTagTest.php
+++ b/tests/Tag/Base/BooleanInputTagTest.php
@@ -12,7 +12,7 @@ final class BooleanInputTagTest extends TestCase
{
public function testChecked(): void
{
- $this->assertSame('', (string) (new TestBooleanInputTag())->checked());
+ $this->assertSame('', (string) (new TestBooleanInputTag())->checked());
$this->assertSame('', (string) (new TestBooleanInputTag())->checked(false));
$this->assertSame('', (string) (new TestBooleanInputTag())
->checked(true)
@@ -64,7 +64,7 @@ public function testLabel(string $expected, ?string $label, array $attributes):
public function testLabelNoWrap(): void
{
$this->assertSame(
- ' ',
+ ' ',
(string) (new TestBooleanInputTag())
->id('ID')
->label('Voronezh', wrap: false),
@@ -74,7 +74,7 @@ public function testLabelNoWrap(): void
public function testLabelWithId(): void
{
$this->assertSame(
- '',
+ '',
(new TestBooleanInputTag())
->id('Test')
->label('One')
@@ -85,7 +85,7 @@ public function testLabelWithId(): void
public function testSideLabel(): void
{
$this->assertMatchesRegularExpression(
- '~ ~',
+ '~ ~',
(new TestBooleanInputTag())
->sideLabel('One')
->render(),
@@ -95,7 +95,7 @@ public function testSideLabel(): void
public function testSideLabelEmpty(): void
{
$this->assertMatchesRegularExpression(
- '~ ~',
+ '~ ~',
(new TestBooleanInputTag())
->sideLabel('')
->render(),
@@ -115,7 +115,7 @@ public function testSideLabelNull(): void
public function testSideLabelWithId(): void
{
$this->assertSame(
- ' ',
+ ' ',
(new TestBooleanInputTag())
->id('Test')
->sideLabel('One')
@@ -126,7 +126,7 @@ public function testSideLabelWithId(): void
public function testSideLabelWithAttributes(): void
{
$this->assertMatchesRegularExpression(
- '~ ~',
+ '~ ~',
(new TestBooleanInputTag())
->sideLabel('One', ['class' => 'red'])
->render(),
@@ -136,7 +136,7 @@ public function testSideLabelWithAttributes(): void
public function testSideLabelId(): void
{
$this->assertSame(
- ' ',
+ ' ',
(new TestBooleanInputTag())
->sideLabel('One')
->id('count')
@@ -160,15 +160,15 @@ public static function dataUncheckValue(): array
return [
['', null, null],
['', null, 7],
- ['', 'color', null],
- ['', 'color[]', null],
+ ['', 'color', null],
+ ['', 'color[]', null],
[
- '',
+ '',
'color',
7,
],
[
- '',
+ '',
'color[]',
7,
],
@@ -191,7 +191,7 @@ public function testUncheckValueDisabled(): void
{
$this->assertSame(
''
- . '',
+ . '',
(new TestBooleanInputTag())
->name('color')
->uncheckValue(7)
@@ -204,7 +204,7 @@ public function testUncheckValueForm(): void
{
$this->assertSame(
''
- . '',
+ . '',
(new TestBooleanInputTag())
->name('color')
->uncheckValue(7)
@@ -217,7 +217,7 @@ public function testUncheckValueWithLabel(): void
{
$this->assertSame(
''
- . '',
+ . '',
(new TestBooleanInputTag())
->name('color')
->uncheckValue(7)
diff --git a/tests/Tag/Base/MediaTagTest.php b/tests/Tag/Base/MediaTagTest.php
index bd8ff6c6..9eef91ab 100644
--- a/tests/Tag/Base/MediaTagTest.php
+++ b/tests/Tag/Base/MediaTagTest.php
@@ -169,9 +169,9 @@ public function testWrongTrackDefault(): void
$this->assertSame(
'' . "\n"
- . '',
$tag->render(),
);
diff --git a/tests/Tag/Base/TagTest.php b/tests/Tag/Base/TagTest.php
index 62ca4310..6eecd831 100644
--- a/tests/Tag/Base/TagTest.php
+++ b/tests/Tag/Base/TagTest.php
@@ -29,7 +29,7 @@ public static function dataAttributes(): array
['', ['class' => ['first', 'second']]],
['', ['class' => []]],
['', ['style' => ['width' => '100px', 'height' => '200px']]],
- ['', ['value' => 42, 'name' => 'position']],
+ ['', ['value' => 42, 'name' => 'position']],
[
'',
[
@@ -94,7 +94,7 @@ public function testReplaceAttributes(): void
public function testUnionAttributes(): void
{
$this->assertSame(
- '',
+ '',
(new TestTag())
->class('red')
->unionAttributes(['class' => 'green', 'id' => 'color'])
diff --git a/tests/Tag/ColgroupTest.php b/tests/Tag/ColgroupTest.php
index c87b15f8..0bd6a80a 100644
--- a/tests/Tag/ColgroupTest.php
+++ b/tests/Tag/ColgroupTest.php
@@ -16,8 +16,8 @@ public function testBase(): void
$this->assertSame(
'' . "\n"
. '' . "\n"
- . '' . "\n"
- . '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
. '',
(new Colgroup())
->columns(
diff --git a/tests/Tag/FormTest.php b/tests/Tag/FormTest.php
index f06cabd5..12cb667a 100644
--- a/tests/Tag/FormTest.php
+++ b/tests/Tag/FormTest.php
@@ -18,7 +18,7 @@ public function testBase(): void
$tag = new Form();
$this->assertSame(
- '',
@@ -36,7 +36,7 @@ public function testBase(): void
public function testGet(): void
{
$this->assertSame(
- '',
+ '',
(new Form())
->get('https://example.com/send')
->render(),
@@ -56,7 +56,7 @@ public function testGetWithoutUrl(): void
public function testPost(): void
{
$this->assertSame(
- '',
+ '',
(new Form())
->post('https://example.com/send')
->render(),
diff --git a/tests/Tag/Input/CheckboxTest.php b/tests/Tag/Input/CheckboxTest.php
index 1608e0b6..2e044ed9 100644
--- a/tests/Tag/Input/CheckboxTest.php
+++ b/tests/Tag/Input/CheckboxTest.php
@@ -12,7 +12,7 @@ final class CheckboxTest extends TestCase
public function testBase(): void
{
$this->assertSame(
- '',
+ '',
(new Checkbox())
->name('number')
->value(42)
diff --git a/tests/Tag/Input/ColorTest.php b/tests/Tag/Input/ColorTest.php
index d543d5ba..ee7d7a22 100644
--- a/tests/Tag/Input/ColorTest.php
+++ b/tests/Tag/Input/ColorTest.php
@@ -12,7 +12,7 @@ final class ColorTest extends TestCase
public function testBase(): void
{
$this->assertSame(
- '',
+ '',
(new Color())
->name('color')
->value('#ff0000')
diff --git a/tests/Tag/Input/FileTest.php b/tests/Tag/Input/FileTest.php
index 820f82ce..774ab650 100644
--- a/tests/Tag/Input/FileTest.php
+++ b/tests/Tag/Input/FileTest.php
@@ -13,7 +13,7 @@ final class FileTest extends TestCase
public function testBase(): void
{
$this->assertSame(
- '',
+ '',
(new File())
->name('avatar')
->render(),
@@ -25,15 +25,15 @@ public static function dataUncheckValue(): array
return [
['', null, null],
['', null, 7],
- ['', 'avatar', null],
- ['', 'avatar[]', null],
+ ['', 'avatar', null],
+ ['', 'avatar[]', null],
[
- '',
+ '',
'avatar',
7,
],
[
- '',
+ '',
'avatar[]',
7,
],
@@ -56,7 +56,7 @@ public function testUncheckValueDisabled(): void
{
$this->assertSame(
''
- . '',
+ . '',
(new File())
->name('avatar')
->uncheckValue(7)
@@ -69,7 +69,7 @@ public function testUncheckValueForm(): void
{
$this->assertSame(
''
- . '',
+ . '',
(new File())
->name('avatar')
->uncheckValue(7)
@@ -89,8 +89,8 @@ public function testUncheckInputAttributes(): void
->render();
$this->assertSame(
- ''
- . '',
+ ''
+ . '',
$result,
);
}
@@ -106,8 +106,8 @@ public function testReplaceUncheckInputAttributes(): void
->render();
$this->assertSame(
- ''
- . '',
+ ''
+ . '',
$result,
);
}
@@ -116,11 +116,11 @@ public static function dataAccept(): array
{
return [
[
- '',
+ '',
null,
],
[
- '',
+ '',
'.doc,.docx',
],
];
@@ -142,11 +142,11 @@ public static function dataMultiple(): array
{
return [
[
- '',
+ '',
true,
],
[
- '',
+ '',
false,
],
];
@@ -167,7 +167,7 @@ public function testMultiple(string $expected, ?bool $multiple): void
public function testMultipleDefault(): void
{
$this->assertSame(
- '',
+ '',
(new File())
->name('avatar')
->multiple()
diff --git a/tests/Tag/Input/RadioTest.php b/tests/Tag/Input/RadioTest.php
index 2ce8214a..144e8d79 100644
--- a/tests/Tag/Input/RadioTest.php
+++ b/tests/Tag/Input/RadioTest.php
@@ -12,7 +12,7 @@ final class RadioTest extends TestCase
public function testBase(): void
{
$this->assertSame(
- '',
+ '',
(new Radio())
->name('number')
->value(42)
diff --git a/tests/Tag/Input/RangeTest.php b/tests/Tag/Input/RangeTest.php
index 689f0e08..eb70b5a6 100644
--- a/tests/Tag/Input/RangeTest.php
+++ b/tests/Tag/Input/RangeTest.php
@@ -21,7 +21,7 @@ public function testBase(): void
->step(10);
$this->assertSame(
- '',
+ '',
$tag->render(),
);
}
@@ -30,11 +30,11 @@ public static function dataMin(): array
{
return [
['', null],
- ['', ''],
- ['', '2.5'],
- ['', 10],
- ['', 42.7],
- ['', new StringableObject('99')],
+ ['', ''],
+ ['', '2.5'],
+ ['', 10],
+ ['', 42.7],
+ ['', new StringableObject('99')],
];
}
@@ -50,11 +50,11 @@ public static function dataMax(): array
{
return [
['', null],
- ['', ''],
- ['', '2.5'],
- ['', 10],
- ['', 42.7],
- ['', new StringableObject('99')],
+ ['', ''],
+ ['', '2.5'],
+ ['', 10],
+ ['', 42.7],
+ ['', new StringableObject('99')],
];
}
@@ -70,11 +70,11 @@ public static function dataStep(): array
{
return [
['', null],
- ['', ''],
- ['', '2.5'],
- ['', 10],
- ['', 42.7],
- ['', new StringableObject('99')],
+ ['', ''],
+ ['', '2.5'],
+ ['', 10],
+ ['', 42.7],
+ ['', new StringableObject('99')],
];
}
@@ -90,7 +90,7 @@ public static function dataList(): array
{
return [
['', null],
- ['', 'DataList'],
+ ['', 'DataList'],
];
}
@@ -124,7 +124,7 @@ public function testAddOutputAttributes(): void
$this->assertSame(
''
- . "\n" . '-',
+ . "\n" . '-',
$tag->render(),
);
}
@@ -181,7 +181,7 @@ public function testOutputWithCustomAttributes(): void
$this->assertMatchesRegularExpression(
'~rangeOutput\d*)\"\)\.innerHTML=this\.value">'
- . "\n" . '-~',
+ . "\n" . '-~',
$tag->render(),
);
}
@@ -193,7 +193,7 @@ public function testOutputWithValue(): void
->value(10);
$this->assertMatchesRegularExpression(
- '~rangeOutput\d*)\"\)\.innerHTML=this\.value">'
. "\n" . '10~',
$tag->render(),
diff --git a/tests/Tag/InputTest.php b/tests/Tag/InputTest.php
index e00f6043..2838d614 100644
--- a/tests/Tag/InputTest.php
+++ b/tests/Tag/InputTest.php
@@ -48,7 +48,7 @@ public function testPassword(): void
public function testFile(): void
{
$this->assertSame(
- '',
+ '',
(string) Input::file('photo', 'c:\\path\\'),
);
}
@@ -56,7 +56,7 @@ public function testFile(): void
public function testCheckbox(): void
{
$this->assertSame(
- '',
+ '',
(string) Input::checkbox('subscribe')->checked(),
);
}
@@ -64,7 +64,7 @@ public function testCheckbox(): void
public function testRadio(): void
{
$this->assertSame(
- '',
+ '',
(string) Input::radio('count', 'one'),
);
}
@@ -72,7 +72,7 @@ public function testRadio(): void
public function testRange(): void
{
$this->assertSame(
- '',
+ '',
(string) Input::range('count', 10),
);
}
@@ -80,7 +80,7 @@ public function testRange(): void
public function testColor(): void
{
$this->assertSame(
- '',
+ '',
(string) Input::color('color', '#ff0000'),
);
}
diff --git a/tests/Tag/LinkTest.php b/tests/Tag/LinkTest.php
index 1b93c46a..549fa899 100644
--- a/tests/Tag/LinkTest.php
+++ b/tests/Tag/LinkTest.php
@@ -13,7 +13,7 @@ final class LinkTest extends TestCase
public function testBase(): void
{
$this->assertSame(
- '',
+ '',
(string) (new Link())
->url('/rss')
->type('application/rss+xml')
@@ -25,7 +25,7 @@ public function testBase(): void
public function testToCssFile(): void
{
$this->assertSame(
- '',
+ '',
(string) Link::toCssFile('main.css'),
);
}
@@ -119,8 +119,8 @@ public function testAs(string $expected, ?string $as): void
public static function dataPreload(): array
{
return [
- ['', '/main.css'],
- ['', '/main.css', 'style'],
+ ['', '/main.css'],
+ ['', '/main.css', 'style'],
];
}
diff --git a/tests/Tag/PictureTest.php b/tests/Tag/PictureTest.php
index a051d13b..d4e399cb 100644
--- a/tests/Tag/PictureTest.php
+++ b/tests/Tag/PictureTest.php
@@ -31,8 +31,8 @@ public function testBase(): void
$this->assertSame(
'' . "\n"
- . '' . "\n"
- . '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
. '
' . "\n"
. '',
$picture->render(),
diff --git a/tests/Tag/SelectTest.php b/tests/Tag/SelectTest.php
index 9883f1cd..6a428e7f 100644
--- a/tests/Tag/SelectTest.php
+++ b/tests/Tag/SelectTest.php
@@ -35,15 +35,15 @@ public static function dataNameForMultiple(): array
{
return [
['', null],
- ['', ''],
+ ['', ''],
[
'' . "\n"
- . '',
+ . '',
'age',
],
[
'' . "\n"
- . '',
+ . '',
'place[]',
],
];
@@ -362,7 +362,7 @@ public function testOptionsAndGroupsAttributes(): void
$this->assertStringContainsStringIgnoringLineEndings(
<<
-
+