Many popular CSS frameworks like Tailwind CSS (https://tailwindcss.com) and USWDS (https://designsystem.digital.gov/) are beginning to use colons in CSS identifiers (class names) in order to support responsive prefixes and state variants. For example, in Tailwind CSS, you can use the following to support different styles in different states:

<button class="bg-transparent hover:bg-blue text-blue-dark hover:text-white...">
  Hover me
</button>

I discovered this issue when trying to create a view with a grid display. When I tried to set the custom CSS class for columns to "grid-col-12 tablet:grid-col-4", I am seeing the colon being stripped out. This class value is being passed through `Html::cleanCssIdentifier` which I believe is stripping the colon.

Comments

rromore created an issue. See original summary.

rromore’s picture

cilefen’s picture

If colons are allowed by the W3 in class names (I guess Tailwinds doesn’t break pseudo-classes in the style sheet?) then this is a bug. But also note the character is configurable: https://tailwindcss.com/docs/configuration/#app

rromore’s picture

Technically, according to the CSS specification, "...identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_); they cannot start with a digit, two hyphens, or a hyphen followed by a digit. Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item). For instance, the identifier 'B&W?' may be written as 'B\&W\?' or 'B\26 W\3F'" (https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier). From just looking at the source of the `Html::cleanCssIdentifier` method, I believe it supports all of these conditions except the last one.

rromore’s picture

I would also just like to quickly note that the patch I've attached to the issue does work, but applying it might produce some unintended consequences depending on the class names you've used for selecting in CSS or JS. For example, I have a view called "News" with a block display. Before applying the patch, the class value of this block div element was "block block-views block-views-blocknews-block-1". After applying the patch, it is now "block block-views block-views-block:news-block-1".

cilefen’s picture

Status: Active » Needs review
Issue tags: +CSS
cilefen’s picture

So an escaped colon is stripped too?

rromore’s picture

Yep, unfortunately. Without patch and without slash:
https://i.imgur.com/NqrbWZb.png
https://i.imgur.com/Zk0bsmH.png

Without patch and with slash:
https://i.imgur.com/UtcAGxh.png
https://i.imgur.com/2q6y7dP.png

With patch and without slash (& with slash since the slash gets removed anyways):
https://i.imgur.com/1wtOCnt.png

Also, thank you for pointing me to the Tailwind CSS configuration options. I am using USWDS and I don't believe it currently supports a configurable separator, and have opened a related issue here: https://github.com/uswds/uswds/issues/3045.

Status: Needs review » Needs work

The last submitted patch, 2: allow_colons_css_identifiers-3050007-2.patch, failed testing. View results

chris burge’s picture

The issue isn't with the colons, specifically. It's with escaped characters more generally. There's a sister issue at #2916377: Html::cleanCssIdentifier Strips Valid Escaped Characters where they're solving for slash and '@' sign (but not colons).

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

mattsmith321’s picture

I'm using the patch provided in #2. I also acknowledge the authors comment in #5 about unintended consequences. With that said, is there a way to update the preg_replace parameters to be less greedy? Or, phrased a little differently, to only allow colons in specific class names that I allow?

Here is an example of the USWDS class names that I am using:
desktop:grid-col-3 tablet:grid-col-6 usa-card mobile-lg:grid-col-12

While USWDS has way too many combos if you factor in the left and right side of the colon (2,828 unique combos to be exact), the left side boils down to:
*desktop:
*hover:
*mobile-lg:
*tablet:

The only challenge might be dealing with the spots that use desktop:hover: / mobile-lg:hover / tablet:hover but I'm not worried about that.

So is there a way to update pattern parameter to include a check for the literal string of "desktop:" or the unicode equivalent? I know there is a way in regex in general and I was trying to work my way through it last night but then killed my installation and couldn't get back even after undoing my changes.

    $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);

Thanks for any help. Yes, I know that putting in these literal exceptions is not a good fix, but I want to avoid the unintended consequences of changing a whole bunch of Drupal generated class names.

mattsmith321’s picture

Well that didn't take long to run into the unintended consequences of the greedy colon replacement. Lots of colons started showing up in the various Drupal managed class names, especially around views and blocks. I spent a couple hours trying to modify the regex pattern to try to narrow it down to something along the lines of \b(desktop|tablet|mobile-lg)\:\b but didn't have much luck. To make it work for now, I added code to replace the colons with a placeholder and then swapped them back out after the stripping call.

    // The USWDS CSS uses colons in CSS identifiers (class names) in order to support
    // responsive prefixes and state variants. By default Drupal strips out the extra colons.
    // An easy fix would have been to add the colon (: or U+003A) to the next section
    // that identifies the valid characters. However, by broadly allowing colons, existing
    // classes are impacted. An example of this greedy replacement is seen in the 
    // Home Page Card div wrapper:
    //   Before: block-views-blockhome-page-cards-provider
    //   After:  block-views-block:home-page-cards-provider
    //
    // To reduce the unintended consequences of broadly replacing 
    // colons in everything, we are going to specifically look for the USWDS occurrences
    // that we are interested in keeping. These are:
    //   - desktop:
    //   - tablet:
    //   - mobile-lg:
    // 
    // A relevant USWDS class descriptor from our site is from the Home Page Cards:
    //   desktop:grid-col-3 tablet:grid-col-6 usa-card mobile-lg:grid-col-12
    //
    // To accomplish our goal, we will replace the existing occurrences with '-safecolon-'.
    // After the stripping in the next secton, we will then revert the occurrences of
    // '-safecolon-' back to regular colons (:). Note that we are replacing all three 
    // identifiers separately initially. We do this to preserve the (desktop|tablet|mobile-lg)
    // uniqueness. But when we go to switch it back, we can replace it with a single line.
    // See https://www.drupal.org/project/drupal/issues/3050007 for additional information.
    $identifier = preg_replace('/\bdesktop\x{003A}\b/u', 'desktop-safecolon-', $identifier);
    $identifier = preg_replace('/\btablet\x{003A}\b/u', 'tablet-safecolon-', $identifier);
    $identifier = preg_replace('/\bmobile-lg\x{003A}\b/u', 'mobile-lg-safecolon-', $identifier);

    // 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);

    // Now we need to replace our -safecolon- placeholder with a regular colon.
    // See notes above about why we are doing this.
    $identifier = preg_replace('/\b\x{002D}safecolon\x{002D}\b/u', ':', $identifier);

I acknowledge it is a bit of a hack but I'm at least trying to narrow the scope of the replacement as much as possible. At the end of the day, I really need to support the USWDS use of colons. We have already created custom twig files in a couple of scenarios just to be able to use the colons in class names. This 'fix' should prevent that from multiplying going forward.

I am not wrapping it in a patch since I don't think this is the best solution that will work for everyone. I am however posting the code in case it helps someone else out.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Wondering if there has been any change? Still an issue for uswd and more gov sites are using uswds now.

cilefen’s picture

I don’t understand the question.

smustgrave’s picture

Just seeing if anyone has come up with a good solution. Thought about whitelisting the class attribute but that doesn't seem right. USWDS library has been picking up steam lately so this is becoming an issue across multiple projects.

rromore’s picture

With the latest versions of USWDS (the library, not the theme) you can change the character that separates the responsive and state prefixes from the main class: https://github.com/uswds/uswds/blob/develop/src/stylesheets/theme/_uswds....

ocastle’s picture

This issue isn't limited to just a colon within libraries. Some libraries also use '@' symbols in their css classes. e.g. uikit. https://getuikit.com/docs/visibility

lendude’s picture

Status: Needs work » Closed (duplicate)
Issue tags: +Bug Smash Initiative
Related issues: +#2916377: Html::cleanCssIdentifier Strips Valid Escaped Characters

Seems like the colon was included in #2916377: Html::cleanCssIdentifier Strips Valid Escaped Characters now too.

I'm going to close this as a duplicate but feel free to re-open this issue is you feel this is addressing a different issue, or that you feel the colon needs a separate issue.