Problem/Motivation
Escaped characters in CSS classes are valid. Several popular CSS/Sass libraries use escaped characters (particularly ones which use the BEM methodology) to denote special uses or characteristics of a CSS class.
For instance, the inuitcss CSS library uses the @ character in CSS classes for identifying responsive breakpoint classes and the forward slash / character for fractional grid column classes.
Example CSS with Escaped Characters
.u-1\/1 {
// CSS here.
}
.u-1\/2\/@tablet {
// CSS here.
}
.u-1\/3\/@desktop {
// CSS here.
}
Example Markup Using CSS classes with escaped characters
<div class="o-layout__item u-1/1 u-1/2@tablet u-1/3@desktop">
The Html::cleanCssIdentifier function strips escaped characters however, making Drupal incompatible with CSS which uses them.
Drupal Example of Problem
In a twig template, using the clean_class filter (which uses Html::cleanCssIdentifier), such as:
{% set classes = [
'o-layout__item',
('u-1/1'|clean_class),
('u-1/2@tablet'|clean_class),
('u-1/3@desktop'|clean_class),
('u-1/3@desktop'|clean_class)
]
%}
<div{{ attributes.addClass(classes) }}>I'm a div with grid classes</div>
produces the following markup:
<div class="o-layout__item u-1-1 u-1-2tablet u-1-3desktop">I'm a div with grid classes</div>
when the markup should instead be:
<div class="o-layout__item u-1/1 u-1/2@tablet u-1/3@desktop">I'm a div with grid classes</div>
Proposed resolution
Update the regex used in the current Html::cleanCssIdentifier
// Valid characters in a CSS identifier are:
// - the hyphen (U+002D)
// - a-z (U+0030 - U+0039)
// - A-Z (U+0041 - U+005A)
// - the underscore (U+005F)
// - 0-9 (U+0061 - U+007A)
// - ISO 10646 characters U+00A1 and higher
// We strip out any character not in the above list.
$identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);
// Identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit.
$identifier = preg_replace([
'/^[0-9]/',
'/^(-[0-9])|^(--)/'
], ['_', '__'], $identifier);
return $identifier;
to allow for escaped characters.
| Comment | File | Size | Author |
|---|---|---|---|
| #54 | 2916377.patch | 1.33 KB | woldtwerk |
| #53 | 2916377-53.patch | 1.36 KB | ibbwebguy |
| #51 | 2916377-51.patch | 1.33 KB | liberatr |
| #46 | 2916377-42.patch | 1.3 KB | gareth.poole |
| #45 | 2916377-41.patch | 1.29 KB | nikolabintev |
Issue fork drupal-2916377
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:
Comments
Comment #2
pcate commentedComment #3
dawehner@PCate Please provide a patch with ideally test coverage as well. I just had a look and your comment makes totally sense to be in line with the w3c standard: https://www.w3.org/TR/CSS21/syndata.html#characters
Comment #5
daniel kulbeComment #6
daniel kulbeI can confirm, this is also an issue for the UIKit framework / theme. Responsive classes are also addressed with @ (U+0040).
So I added a patch, which covers the "@ sign", use by many CSS frameworks in responsive breakpoint selectors, and the "slash", used by some CSS frameworks as grid element width selector.
While this is still opinionated, as most characters will work as escaped character in a Stylesheet file, while there is no need to escape those in a tag attribute.
Comment #8
pcate commentedI tested the patch and it works great!
Opinionated yes, but it is a pragmatic fix by adding support for the 2 most common escaped characters CSS frameworks use.
Comment #9
pcate commentedComment #12
chris burge commentedThe issue isn't with the slash and the '@' sign, specifically. It's with escaped characters more generally. There's a sister issue at #3050007: CSS identifiers created via `Html::cleanCssIdentifier` shouldn't strip colons where they're solving for colons (but not for slash or '@' sign).
Comment #13
pcate commentedI'm wondering if the best way to solve this issue is to use a configuration flag/setting similar to the
$conf['allow_css_double_underscores']setting in D7 that allowed BEM double underscore CSS selectors.A similar flag could allow developers to set what escaped characters they want to whitelist with the
cleanCssIdentifierfunction. By default it could be none, which wouldn't cause a BC with older D8 versions.Comment #16
jptillman commentedIs this being worked on by anyone? Drupal8 is currently not compatible with Tailwindcss classes due to this problem -- specifically because it uses "/" in class identifiers. Therefore, these classes can't be used in Views configs and similar. I'd like to fix this somehow.
I'm very confused about how the submitted patches actually worked, since there's a line above the patch-modded lines which uses the default filter (which specifies a "/" should be changed to "-") to modify the identifier.
Perhaps the testers only cared about "@" et al, and not "/"?
Comment #17
pcate commented@jptillman I don't believe anyone is working on this issue. @Daniel Kulbe patch only solves for the @ symbol. @Chris Burge links to a separate issue dealing with the ":" symbol.
Ideally there would be one issue working to resolve this for all escaped characters, but that would require more coordination and probably input from core maintainers.
There is some question as to whether this would constitute a BC or not. I proposed a configuration flag/setting similar to D7's
$conf['allow_css_double_underscores']method if it was, so the fix could be used until D10 when it could be set as the default.If you would like to start things by creating a patch that fixes the issue for all escape characters you can assign the issue to yourself while working on it.
Comment #18
chris burge commentedI agree we should wrap any of the related issues into a single issue and handle them together.
I hadn't thought about the BC aspect, but that will be the challenging part. Here's the risk: we change how
::cleanCssIdentifier()processes strings and one or more class names cleaned with this method are changed. The result would be CSS regressions on sites.Here's an alternative to adding a flag: Deprecate
::cleanCssIdentifier()and add a new method. The problem with that approach is that::cleanCssIdentifier()is used all over core. This solution helps with contrib but not with core.Adding a flag in config might be the way to go.
Comment #20
pcate commentedI did some research into some of the most popular CSS frameworks to try and get a sense of what escaped special characters are being used in the wild. Below are my findings.
If anyway knows of others please add.
inuitcss
@ /Tailwind
: .UIKit
@U.S. Web Design System (USWDS)
:Comment #21
heddnThis merges in the fine work from #3050007: CSS identifiers created via `Html::cleanCssIdentifier` shouldn't strip colons.
Comment #23
heddnComment #25
pcate commented@heddn we'll also need to exclude the period character
.for compatibility with TailwindCSS 2.0. https://tailwindcss.com/docs/margin is one example of this.Comment #26
mglamanAdding U+002E for the period character supports margin in Tailwind.
https://www.phpliveregex.com/p/yxz
Working on a patch and test fixes.
Comment #27
mglamanHere is a patch and I fixed the DisplayBlockTest::testBlockEmptyRendering rendering test. But it seems like supporting
:and.in class names will cause backwards compatibility problems in class names.Comment #29
jptillman commentedAny chance someone might roll a patch for D8? It's a problem there, too, and the latest patch won't apply.
Comment #30
suresh prabhu parkala commentedRe-rolled patch. Please review.
Comment #33
heddnRe-roll. Conflicts where in the test class.
Comment #34
pcate commentedI'm tagging this issue as "Needs frontend framework manager review" since I think this issue needs guidance on if/how to handle the BC aspect of this change, given its potential scope of impact.
Comment #35
dakwamineI cannot use underscore in views CSS classes; it gets replaced by a hyphen.
It goes through
Html::cleanCssIdentifier(...). The underscore is removed by the str_replace which uses the $filter character list (which contains the underscore by default), even before thepreg_replaceprocesses the identifier.The line with
converts valid underscore into hyphens.
If I remove the default underscore value in the argument, it works.
I understand this change is prone to create unexpected results, especially when existing theme CSS classes were based on the previous behaviour. Alas, I don't have any idea on how views can customize the filter variable.
Comment #36
dakwamineAnd with the previous test, I have the class in both formats. Not sure why.
Comment #37
dakwamineAfter further investigation, now I know why there are both formats. There is a backwards compatibility functionality in the template_preprocess_views_view which calls the cleanCssIdentifier:
cleanCssIdentifier is called in a callback passed to an array_map in views.theme.inc, which may be reworked for an additional option alongside the css_class one. So this is a views module specific processing which is not really related to this very issue.
Regarding our issue here, I was in a rush, so I decided to patch the cleanCssIdentifier method by doing a simple check on the identifier:
... so that anywhere I want to keep the underscores, I only need to prefix my class with the 'keep_underscores___' string. Not the prettiest way to handle this, but it works and does not impact existing classes.
Comment #38
heddnRe-roll.
Comment #40
heddnThis shouldn't make test failures any worse. The css selectors in the block test were already re-worked and aren't in need of updates any longer.
Comment #42
scotwith1tIncredibly important patch for anyone using the USWDS framework and many others. Thanks @heddn and others! Not sure what else is needed to get this to land, other than passing tests, but hope to see this one in a release soon! :)
Comment #43
scotwith1tJust an additional note that this has a side effect of changing how
clean_classtwig filter transforms variables into class names (since it usescleanCssIdentifier). For example, we had a block which had:Previously, the plugin_id had its : stripped out and the css was written and applied to that class. After applying the patch in #40, the class name changed because
clean_classno longer stripped that colon out of the plugin_id. Not a huge deal, but something I hadn't anticipated and had to do a (very minor) css update to address. I would assume there could be other unexpected side effects for other places/modules that use thecleanCssIdentifiermethod of this class.Comment #45
nikolabintev commentedI am uploading a patch that can be applied to the latest stable Drupal 10 version: 10.2.4
Comment #46
gareth.poole commentedThis patch seems to work with 10.3
Comment #47
liberatrThe patch in #46 removes the comment that mentions period:
// - the period (U+002E)It is still present in regex, just not in comment.
Comment #48
sea2709 commentedIMHO, I agree that we should allow more characters in CSS classes like Tailwind CSS classes with colon, brackets, etc. But I'm concerned when we makes changes for the function
cleanCssIdentifier, since this function is used to generate a string/class for use as a CSS identifier.For example, I place a field block in layout builder, that field block has a class like
block-field-block:paragraph:grid:field-columnif we allow colons incleanCssIdentifier, originally, this class isblock-field-blockparagraphgridfield-column.I wonder if generated classes should have colons, and it might impact an existing site which has many elements being styled based on the generated classes. We should think twice before applying changes for
cleanCssIdentifier.Comment #49
chris burge commented@sea2709 - That's exactly the issue. We can't change
Html::cleanCssIdentifierwithout causing CSS bugs.#13
I think the behavior should be configurable. Maybe a $setting to define additional characters?
Comment #50
yazzbe commented> I think the behavior should be configurable. Maybe a $setting to define additional characters?
That is a great idea!
Just like in the block_class module where it is also configurable to allow special characters in classes.
Comment #51
liberatrRerolled the 10.3 patch from #46 to include the missing comment.
Comment #52
ibbwebguy commentedI believe the order of unicode characters is wrong and will result in unwanted characters being valid. The correct order should be:
$identifier = preg_replace('/[^\x{002D}\x{002E}\x{002F}\x{0030}-\x{0039}\x{003A}\x{0040}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);Alternately, the syntax could be shortened to the following, but then the alphanumeric ranges would not be specifically listed.
$identifier = preg_replace('/[^\x{002D}-\x{003A}\x{0040}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);Comment #53
ibbwebguy commentedRerolled the 10.3 patch from #51 to include the correct unicode regex.
Comment #54
woldtwerk commented11.2 patch
Comment #56
kopeboyEven ":" (colon) is not allowed, but it's used by TailwindCSS. For example I can't enter
grid gap-4 md:grid-cols-2 xl:grid-cols-3in the View CSS class.