Skip to content

Commit 42c12fa

Browse files
mpdudefabpot
authored andcommitted
Add an html_attr function to make outputting HTML attributes easier
1 parent 0319c82 commit 42c12fa

File tree

15 files changed

+1397
-0
lines changed

15 files changed

+1397
-0
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Add support for renaming variables in object destructuring (`{name: userName} = user`)
44
* Add `html_attr_relaxed` escaping strategy that preserves :, @, [, and ] for front-end framework attribute names
55
* Add support for short-circuiting in null-safe operator chains
6+
* Add the `html_attr` function and `html_attr_merge` as well as `html_attr_type` filters
67

78
# 3.23.0 (2026-01-23)
89

doc/filters/html_attr_merge.rst

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
``html_attr_merge``
2+
===================
3+
4+
.. _html_attr_merge:
5+
6+
.. versionadded:: 3.24
7+
8+
The ``html_attr_merge`` filter was added in Twig 3.24.
9+
10+
The ``html_attr_merge`` filter merges multiple mappings that represent
11+
HTML attribute values. Such mappings contain the names of the HTML attributes
12+
as keys, and the corresponding values represent the attributes' values.
13+
14+
It is primarily designed for working with arrays that are passed to the
15+
:ref:`html_attr` function. It closely resembles the :doc:`merge <../filters/merge>`
16+
filter, but has different merge behavior for values that are iterables
17+
themselves, as it will merge such values in turn.
18+
19+
The filter returns a new merged array:
20+
21+
.. code-block:: twig
22+
23+
{% set base = {class: ['btn'], type: 'button'} %}
24+
{% set variant = {class: ['btn-primary'], disabled: true} %}
25+
26+
{% set merged = base|html_attr_merge(variant) %}
27+
28+
{# merged is now: {
29+
class: ['btn', 'btn-primary'],
30+
type: 'button',
31+
disabled: true
32+
} #}
33+
34+
The filter accepts multiple arrays as arguments and merges them from left to right:
35+
36+
.. code-block:: twig
37+
38+
{% set merged = base|html_attr_merge(variant1, variant2, variant3) %}
39+
40+
A common use case is to build attribute mappings conditionally by merging multiple
41+
parts based on conditions. To make this conditional merging more convenient, filter
42+
arguments that are ``false``, ``null`` or empty arrays are ignored:
43+
44+
.. code-block:: twig
45+
46+
{% set button_attrs = {
47+
type: 'button',
48+
class: ['btn']
49+
}|html_attr_merge(
50+
variant == 'primary' ? { class: ['btn-primary'] },
51+
variant == 'secondary' ? { class: ['btn-secondary'] },
52+
size == 'large' ? { class: ['btn-lg'] },
53+
size == 'small' ? { class: ['btn-sm'] },
54+
disabled ? { disabled: true, class: ['btn-disabled'] },
55+
loading ? { 'aria-busy': 'true', class: ['btn-loading'] },
56+
) %}
57+
58+
{# Example with variant='primary', size='large', disabled=false, loading=true:
59+
60+
The false values (secondary variant, small size, disabled state) are ignored.
61+
62+
button_attrs is:
63+
{
64+
type: 'button',
65+
class: ['btn', 'btn-primary', 'btn-lg', 'btn-loading'],
66+
'aria-busy': 'true'
67+
}
68+
#}
69+
70+
Merging Rules
71+
-------------
72+
73+
The filter follows these rules when merging attribute values:
74+
75+
**Scalar values**: Later values override earlier ones.
76+
77+
.. code-block:: twig
78+
79+
{% set result = {id: 'old'}|html_attr_merge({id: 'new'}) %}
80+
{# result: {id: 'new'} #}
81+
82+
**Array values**: Arrays are merged like in PHP's ``array_merge`` function - numeric keys are
83+
appended, non-numeric keys replace.
84+
85+
.. code-block:: twig
86+
87+
{# Numeric keys (appended): #}
88+
{% set result = {class: ['btn']}|html_attr_merge({class: ['btn-primary']}) %}
89+
{# result: {class: ['btn', 'btn-primary']} #}
90+
91+
{# Non-numeric keys (replaced): #}
92+
{% set result = {class: {base: 'btn', size: 'small'}}|html_attr_merge({class: {variant: 'primary', size: 'large'}}) %}
93+
{# result: {class: {base: 'btn', size: 'large', variant: 'primary'}} #}
94+
95+
.. note::
96+
97+
Remember, attribute mappings passed to or returned from this filter are regular
98+
Twig mappings after all. If you want to completely replace an attribute value
99+
that is an iterable with another value, you can use the :doc:`merge <../filters/merge>`
100+
filter to do that.
101+
102+
**``MergeableInterface`` implementations**: For advanced use cases, attribute values can be objects
103+
that implement the ``MergeableInterface``. These objects can define their own, custom merge
104+
behavior that takes precedence over the default rules. See the docblocks in that interface
105+
for details.
106+
107+
.. note::
108+
109+
The ``html_attr_merge`` filter is part of the ``HtmlExtension`` which is not
110+
installed by default. Install it first:
111+
112+
.. code-block:: bash
113+
114+
$ composer require twig/html-extra
115+
116+
Then, on Symfony projects, install the ``twig/extra-bundle``:
117+
118+
.. code-block:: bash
119+
120+
$ composer require twig/extra-bundle
121+
122+
Otherwise, add the extension explicitly on the Twig environment::
123+
124+
use Twig\Extra\Html\HtmlExtension;
125+
126+
$twig = new \Twig\Environment(...);
127+
$twig->addExtension(new HtmlExtension());
128+
129+
Arguments
130+
---------
131+
132+
The filter accepts a variadic list of arguments to merge. Each argument can be:
133+
134+
* A map of attributes
135+
* ``false`` or ``null`` (ignored, useful for conditional merging)
136+
* An empty string ``''`` (ignored, to support implicit else in ternary operators)
137+
138+
.. seealso::
139+
140+
:ref:`html_attr`,
141+
:doc:`html_attr_type`

doc/filters/html_attr_type.rst

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
``html_attr_type``
2+
==================
3+
4+
.. _html_attr_type:
5+
6+
.. versionadded:: 3.24
7+
8+
The ``html_attr_type`` filter was added in Twig 3.24.
9+
10+
The ``html_attr_type`` filter converts arrays into specialized attribute value
11+
objects that implement custom rendering logic. It is designed for use
12+
with the :ref:`html_attr` function for attributes where
13+
the attribute value follows special formatting rules.
14+
15+
.. code-block:: html+twig
16+
17+
<img {{ html_attr({
18+
srcset: ['small.jpg 480w', 'large.jpg 1200w']|html_attr_type('cst')
19+
}) }}>
20+
21+
{# Output: <img srcset="small.jpg 480w, large.jpg 1200w"> #}
22+
23+
Available Types
24+
---------------
25+
26+
Space-Separated Token List (``sst``)
27+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28+
29+
Used for attributes that expect space-separated values, like ``class`` or
30+
``aria-labelledby``:
31+
32+
.. code-block:: html+twig
33+
34+
{% set classes = ['btn', 'btn-primary']|html_attr_type('sst') %}
35+
36+
<button {{ html_attr({class: classes}) }}>
37+
Click me
38+
</button>
39+
40+
{# Output: <button class="btn btn-primary">Click me</button> #}
41+
42+
This is the default type used when the :ref:`html_attr` function encounters an
43+
array value (except for ``style`` attributes).
44+
45+
Comma-Separated Token List (``cst``)
46+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
47+
48+
Used for attributes that expect comma-separated values, like ``srcset`` or
49+
``sizes``:
50+
51+
.. code-block:: html+twig
52+
53+
<img {{ html_attr({
54+
srcset: ['image-1x.jpg 1x', 'image-2x.jpg 2x', 'image-3x.jpg 3x']|html_attr_type('cst'),
55+
sizes: ['(max-width: 600px) 100vw', '50vw']|html_attr_type('cst')
56+
}) }}>
57+
58+
{# Output: <img srcset="image-1x.jpg 1x, image-2x.jpg 2x, image-3x.jpg 3x" sizes="(max-width: 600px) 100vw, 50vw"> #}
59+
60+
Inline Style (``style``)
61+
~~~~~~~~~~~~~~~~~~~~~~~~
62+
63+
Used for style attributes. Handles both maps (property - value pairs) and sequences (CSS declarations):
64+
65+
.. code-block:: html+twig
66+
67+
{# Associative array #}
68+
{% set styles = {color: 'red', 'font-size': '14px'}|html_attr_type('style') %}
69+
70+
<div {{ html_attr({style: styles}) }}>
71+
Styled content
72+
</div>
73+
74+
{# Output: <div style="color: red; font-size: 14px;">Styled content</div> #}
75+
76+
{# Numeric array #}
77+
{% set styles = ['color: red', 'font-size: 14px']|html_attr_type('style') %}
78+
79+
<div {{ html_attr({style: styles}) }}>
80+
Styled content
81+
</div>
82+
83+
{# Output: <div style="color: red; font-size: 14px;">Styled content</div> #}
84+
85+
The ``style`` type is automatically applied by the :ref:`html_attr` function when
86+
it encounters an array value for the ``style`` attribute.
87+
88+
.. note::
89+
90+
The ``html_attr_type`` filter is part of the ``HtmlExtension`` which is not
91+
installed by default. Install it first:
92+
93+
.. code-block:: bash
94+
95+
$ composer require twig/html-extra
96+
97+
Then, on Symfony projects, install the ``twig/extra-bundle``:
98+
99+
.. code-block:: bash
100+
101+
$ composer require twig/extra-bundle
102+
103+
Otherwise, add the extension explicitly on the Twig environment::
104+
105+
use Twig\Extra\Html\HtmlExtension;
106+
107+
$twig = new \Twig\Environment(...);
108+
$twig->addExtension(new HtmlExtension());
109+
110+
Arguments
111+
---------
112+
113+
* ``value``: The sequence of attributes to convert
114+
* ``type``: The attribute type. One of:
115+
116+
* ``sst`` (default): Space-separated token list
117+
* ``cst``: Comma-separated token list
118+
* ``style``: Inline CSS styles
119+
120+
.. seealso::
121+
122+
:ref:`html_attr`,
123+
:ref:`html_attr_merge`

doc/filters/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Filters
2626
format_datetime
2727
format_number
2828
format_time
29+
html_attr_merge
30+
html_attr_type
2931
html_to_markdown
3032
inline_css
3133
inky_to_html

0 commit comments

Comments
 (0)