This is related to #3301373: Create twig |add_suggestion filter
In conjunction with the |add_suggestion filter, themers typically want to set CSS classes on the field components to maintain proper BEM architecture.
The issue above has this discussion to add this functionality into that filter, but in a subsequent Slack conversation @lauriii suggested splitting it into its own |add_class filter, along with a |setAttribute filter.
This would enable something like
{{ content.field_event__links|add_suggestion('list')|add_class('event__link-list') }}
{# Produces: #}
<ul class="list event__link-list">
<li><a href="/">foo</a></li>
<li><a href="/">bar</a></li>
</ul>and
{{ content.field_event__links|as('list')|add_class('event__link-list')|set_attribute('data-kitten', 'mila') }}
{# Produces: #}
<div class="field__items field--tags__items event__link-list"> data-kitten="mila">
<!-- rest of the markup -->
</div>
Release highlight
New Twig |add_class and |add_suggestion filters to set CSS classes or attributes on field render arrays. See https://www.drupal.org/project/drupal/issues/3301853 for details.
| Comment | File | Size | Author |
|---|---|---|---|
| #53 | 3301853-53.patch | 6.63 KB | mherchel |
| #46 | node_html_twig_—_drupal_and_Appellatio_Iaceo_Minim_Pecus___Drupal.png | 164.18 KB | mherchel |
| #43 | 3301853-nr-bot.txt | 127 bytes | needs-review-queue-bot |
| #40 | 3301853-nr-bot.txt | 127 bytes | needs-review-queue-bot |
| #37 | 3301853-create-twig-filters__with_array_is_list_loop.patch | 1.52 KB | pdureau |
Issue fork drupal-3301853
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3301853-create-twig-filters
changes, plain diff MR !2752
Comments
Comment #2
mherchelComment #3
mherchelComment #4
lauriiiSomething to take into account on the implementation is that we should probably support attributes both in arrays and in
\Drupal\Core\Template\Attributeobject.Comment #5
mherchelComment #6
matthieuscarset commentedStarting to work on this issue
Comment #8
matthieuscarset commentedImplemented the
add_classfilter in this PR.Here's an example of the markup I used to test this filter:
Pending on creating functional tests but I wanted to have this code reviewed before going any further 🤷.
Comment #9
anchal_gupta commentedFix Custom command error. please review
Comment #10
mherchelDoing some informal testing on this, and so far its looking really good! I added a class, and even intentionally added some invalid characters. It worked well. I also tested the filter out if the field was not present in the view mode (it did not blow up and worked as expected).
So far this is great!
Next steps are the add the set_attribute, and remove_class filters. You'll also obviously need to fix the coding standards issues and write tests.
I'll reach out to @lauriii while at DrupalCon to see if he can take a quick look at the approach in the code.
Thanks for working on this!
Comment #11
matthieuscarset commentedAll filters are implemented and work as expected - based on local testing with this kind of markup:
Still pending:
core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.phpComment #12
mherchelThis is really exciting! I did some initial testing by creating a new field called
field_test.The
add_classseems to work great.I could not get the
remove_classfilter to work at all. I tested out{{ content.field_test|remove_class('field') }}. Thefieldclass was still present.The
set_attributeclass worked. However, if only one parameter was passed (eg{{ content.field_test|set_attribute('test') }}), the filter silently fails. I would expect it to fail and generate an exception and let the developer know what the problem is.Comment #13
matthieuscarset commentedThank you for this review.
As far as I know, there's no way to actually remove a class from

{{ content.field_XXX }}(e.g. from a Node template) because it's a render array without any#attributesyet. Here's an example of the content the render array:In your test, I understand you want to remove the
.fieldclass but this class does not actually exist yet - it will be added within thefield.html.twigtemplate later during the rendering process.I don't see solutions here. Maybe someone else has an idea/workaround?
Regarding
set_attributefilter, I thought it was useful to allow the NULL value as it gives the possibility to remove an attribute but as mentioned above, this won't necessarily work if you target an attribute not already rendered. So your comment makes me think that we should set the attribute value as an empty string by default so that it is actually set. Example:{{ content.field_test|set_attribute('data-custom-attribute', '') }}will result in<div data-custom-attribute></div>.Committing this change now.
Comment #14
mherchelThanks for looking into it! I'll ask @lauriii to look into the remove_class issue today or tomorrow.
Comment #15
mherchelMoved the
|remove_classfilter into followup #3314482: Add |remove_class twig filterComment #16
matthieuscarset commentedBoth
|add_classand|set_attributefilters have been rewritten to match existing code/methods.Now working on Unit tests.
Comment #17
matthieuscarset commentedHappy to say
add_classandset_attributefilters are done and Unit tests implements for both.MR currently mergeable so ready for review👌
Comment #18
chi commentedI think these filters should not care about cleaning and validation provided values. They are meant for developers and should set exactly what was given to them because magic may confuse. Garbage in, garbage out.
Meanwhile updating element attributes can be done with Twig Tweak.
{{ element|with(['#attributes', 'name'], 'value') }}Comment #19
matthieuscarset commentedThank you very much for the review and comments. I'll reply in Gitlab.
I agree there are better/quicker/more advanced filters in Twig Tweak. @Chi You might want to port
|withfilter here - in another PR - to replaceadd_classandset_attribute?Comment #20
mherchelThis is so exciting!
One thing that I noticed is that if you only pass one parameter to
|set_attribute, it silently does nothing. IMO it should either fail (with a useful error message), or (best case) just output the attribute without any value.Comment #21
chi commentedwithcannot fully replaceadd_classas it requires knowing array index, i.e. class position.Also I propose we do not reference filters here as
|filter_name, it's a bit confusing and not consistent with documentation in other places. )))Comment #22
matthieuscarset commentedAnswering #20: The idea with allowing NULL value in
|set_attribute('alt')was to be able to reset an attribute. I might be wrong but if an attribute is null it will ultimately not be rendered in the HTML. Therefore we empower themers by allowing the removal of existing attributes.Answering #21: Thanks for the explanation - I understand
add_classis still a relevant filter to have in core. About mentioning the filter name in assert, I thought the same in the first place but I've seen other assertions with such mentions so I tried to stay consistent and did the same (e.g. suggestThemeHook()).Comment #23
chi commented@matthieuscarset I requested native typehints because #3154762: [policy, no patch] All new Drupal 10 code should use appropriate type hints wherever possible is basically accepted. It's just waiting for change record.
Comment #24
chi commentedA note of the classes parrameter.
They could be documented this way.
Comment #25
chi commentedTo clarify. The docblock stays that $classes variable is either a variadic string or array of strings. There is no chance for it to be an object.
If we do need to support stringable object then the documentation needs to be updated. Something like this.
And native PHP typehint could be like follows.
The documentation could be a bit simpler if we use is_variadic option.
Comment #26
mherchelBumping this to the top of people's queues (since GitLab comments do not).
Comment #27
mherchelTest failing.
Comment #28
matthieuscarset commentedFailing test has been fixed but latest commit does not appear on the MR.
Commit is here though 🤷 Not sure if there is something I should do to force update the MR or if it is just a matter of time.
Comment #29
mherchelI have no clue what's going on. Maybe do a
git reset --hardor something? If not, ask in the slack #gitlab channelComment #30
mherchelSetting back to needs work based on @lauriii's review
Thanks for all the work on this @matthieuscarset. It seems like we're really nearing the finish line!
Comment #31
mherchelComment #32
mherchelBumping to to the top of everyone's dashboard (since Gitlab comments do not do this)
Comment #33
smustgrave commentedThis issue is being reviewed by the kind folks in Slack, #need-reveiw-queue. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge require as a guide.
This looks like a fantastic feature!
Reviewing MR 2752 and the changes look good.
Think with this new filter it will need a change record (maybe release note?)
Comment #34
mherchelSetting back to NR based on https://www.drupal.org/docs/develop/issues/fields-and-other-parts-of-an-..., which states
From what I understand the MR is fine, but we just need a CR (and the tag is already added)
Comment #35
mherchelChange record created at https://www.drupal.org/node/3334294
Comment #36
smustgrave commentedTested this out by altering a template in olivero
node--teaser.html.twig by adding
{{ content.field_image|add_class('my-class')|set_attribute('aria-hidden', 'true') }}When I checked on Drupal 10.1.x with a standard install I can see that a class and attribute is being added correctly..
Comment #37
pdureau commentedHello,
Tested in three of my D10 projects. Thanks a lot for this strong improvement of the themer experience.
However, a common situation is not handled: a renderable variable can be a list of renderables.
Example:
Or simply this:
In this situation, we want the classes and the attributes to be applied to all render arrays of the list.
I have a patch to propose, but i am not able to push my commit (this is my first contribution to a Drupal core issue, so I may do something wrong):
Anyway, the patch is attached here.
Comment #38
matthieuscarset commentedThank you very much for testing this new filter and for reporting your issue.
IMHO I think you should handle your use case differently. Rather than having a the filter to deal with lists, it is simpler to do the loop in your Twig template directly, such as:
Does it work for you?
Comment #39
pdureau commentedBonjour Mathieu,
Thanks for you feedback.
I am afraid it will not work because the themers don't know if the renderable they print is a single render array or a list of them.
For example, without talking about those new filters, because a list of renderable is also a renderable, it is already safer for themers to print directly the variable:
Without knowing if they are getting something like this:
Or something like this:
Doing a loop will break the first case, and may be considered as something to avoid:
Maybe I am caring that much about this use case because I am working with design system integration with my clients, where the Drupal themer is not aware of what will be the business usage of his/her templates. In a more traditional project, where the theming team is a part of the project team, they can figure it out together, adapting the Twig code to what Drupal is sending.
However, I am convinced that considering this use case will make the filters more solid, and is a must-have for contrib themes and modules, which are also developed ahead of their business usages.
Comment #40
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #41
lauriiiThanks for the feedback @pdureau! That seems like a valid use case, but OTOH we don't generally apply filters across child elements like what you are proposing. I'm wondering if this could be handled on the render array level by introducing a new type that allows inheriting properties to children?
Something like this:
I guess there could be other ways to create similar functionality. I'm not too keen to what I'm proposed here so if someone has better ideas, I'd be open to that.
Comment #42
matthieuscarset commentedI understand the request @pdureau. It would be nice to have a filter which automagically
add_class/set_attributeon all children items but I don't think it is a valid use case for this issue specifically.It is quite a specific need. I don't see why
add_classwould want iterate on all children everytime it is called. In your example, you could have a container, a details, a fieldset, a list...etc with children elements. In this case, the themer want to useadd_classto alter the container, all the potential existing children items.I think what you are looking for is a custom Twig filter - like
add_class_recursivelyoradd_class_on_children-which would iterate on all children items to actually apply
add_class/set_attribute. IMHO such a filter should live in a custom module.Comment #43
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #44
pdureau commentedHei Laurii, salut Matthieu,
My request was not about recursively altering all children items, but only the ones wrapped into lists (arrays with only continuous numeric keys), without traversing the children keyed with a string. So, it will be less impactful than expected, no mess with "containers, details, fieldsets, lists"...
It may look like a specific need, but I met it many times in my projects : When templates are created ahead of their usage and a variable expect a list of renderables, we have no way to be sure the templates "users" (presenter templates, render arrays, plugins...) are sending such a list instead of a single renderable.
Thanks for your two proposals (
#type' => 'inherit'andadd_class_recursively...) but let's put them apart for now.I will try to do progress on this subject at contrib level (probably in the scope of UI Patterns module), staying compatible with the current core proposal.
Comment #45
mherchelDiscussed this with @lauriii to figure out what we want to do to move this forward. I've opened up a followup issue to discuss what should happen when we run into an array of render arrays: #3354683: Decide how Twig filters should process against an array of render arrays
Setting this back to NR for now, since it looks like a random failure kicked it back to NW. I will review this shortly!
Pinging both @nod_ and @ckrina in Slack to get their thoughts (per @lauriii)
Comment #46
mherchelThis is still looking great! With the array of render arrays discussion moved to #3354683: Decide how Twig filters should process against an array of render arrays, moving this back to RTBC so we can hopefully get into 10.1.

Comment #47
ckrinaI'll wait for @nod_ to weight in, but +1 to this change. Also code looks fine for me.
Comment #49
dwwCool, exciting to see this close to getting into core! I helped @matthieuscarset for quite some time at a previous $dayJob to understand the automated testing changes for this.
I pushed a commit to fix some trivial docs nits I saw in the current MR. I wouldn't downgrade to NR for that. However, Gitlab thinks this needs a rebase, and when I tried to do so locally, the git commits on this MR branch are full of duplicate commits from 10.1.x core. Regular rebase fails miserably, and interactive rebase is huge and tedious to untangle. I don't want to make even more of a mess of this, so I'm going to stop now and see if @mherchel and/or @matthieuscarset can do the needed rebase to get this actually ready to merge.
Thanks!
-Derek
Comment #50
nod_+1 to adding the methods, for the implementation what chx said in chat makes sense: adding a couple of methods to AttributeHelper object. Maybe if not in this issue it can be done in #3314482: Add |remove_class twig filter.
Assumptions of #44 makes sense to me as well, looking forward to #3354683: Decide how Twig filters should process against an array of render arrays.
Comment #51
matthieuscarset commented@dww I rebased onto
10.1.xand somehow end up pushing +130 changes 😱I'm not sure how to undo this.
Any help will be much appreciated.
Comment #52
mherchel😆 I've totally done this before! Reminds me of this website: https://ohshitgit.com/
I have some work to do, but afterwards I'll attempt to clean this up if no one beats me to it.
Comment #53
mherchelThat MR is beyond FUBAR now.
I talked with @lauriii, and he suggested I post a patch with the same changes at the point it was RTBC'd. I'm setting it to NR so @matthieuscarset can verify the changes in the patch are exactly the same as within the MR before the failed rebase.
Comment #54
matthieuscarset commentedTested the patch. It applies successfully on
10.1.xand changes all look correct 👌Thank you for your help @mherchel
Comment #55
matthieuscarset commentedTests passed. Marking this issue as RTBC
Comment #56
mherchelChanging the actual status field to RTBC per #55 😆
Comment #57
dwwRe-confirming the patch in #53 matches what I reviewed and includes the doc fixes I pushed.
$RTBC++;
Also, updating credit:
Comment #58
rlhawkI'm glad to have discovered this issue, since I created a module, Twig Attributes, a few years ago to scratch a similar itch. However, my focus was on allowing attributes to be added to child elements, such as a link theme hook in a link field, similar to what @pdureau brought up earlier in this thread. I recently added a new filter to Twig Attributes named
add_class, but I'll revert that, since it will conflict with one of the filters being proposed in this issue.I am curious if there was any consideration given to supporting setting classes or attributes on variables other than
attributes, such astitle_attributes,content_attributes, ordescription_attributes, which appear to be the most common attributes variables besidesattributes. I didn't see any mention of that option in this issue, but it seems as though it would be useful.Comment #59
rlhawkIf we wanted to support setting a class or other attribute on variables other than
attributes, perhaps the variable name (or part thereof) could be passed in as an optional additional argument that, if omitted, would default toattributes.{{ content.field_event__links|as('list')|add_class('event__link-list', 'title')|set_attribute('data-kitten', 'mila', 'content') }}The above would add the event__link-list class to
title_attributesand assign "mila" to the data-kitten attribute oncontent_attributes.It would be a shame to add this great feature, but only for the one attributes variable.
Comment #61
ckrinaThanks @dww for helping with the issue credit and everybody for the work.
@rlhawk I'm sure your suggestions could be addressed in followup issues so we don't block this useful feature any longer. :)
Committed 4d500c5 and pushed to 10.1.x. Thanks all!
Comment #62
ckrina