Skip to content

Allow trailing dash in functional utility names#19696

Merged
RobinMalfait merged 3 commits intotailwindlabs:mainfrom
kirkouimet:fix/allow-trailing-dash-in-functional-utility-names
Feb 19, 2026
Merged

Allow trailing dash in functional utility names#19696
RobinMalfait merged 3 commits intotailwindlabs:mainfrom
kirkouimet:fix/allow-trailing-dash-in-functional-utility-names

Conversation

@kirkouimet
Copy link
Contributor

Problem

Tailwind 4.2.0 introduced stricter @utility name validation (#19524) that rejects functional utility names where the root ends with a dash after stripping the -* suffix. This breaks a valid and useful naming pattern where a double dash separates the CSS property from a value scale:

@utility border--* {
  border-color: --value(--color-border-*, [color]);
}

This produces: border--0, border--1, border--2, etc.

The error message is:

@utility border--* defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter.

Why this pattern matters

The double-dash convention creates a clear visual grammar in class names. The first segment names the CSS property, and the double dash separates it from the semantic scale value. In a dense className string like border border--0 background--0 content--4, the scale values (0, 0, 4) are immediately scannable, distinct from the single-dash property names around them.

This pattern is actively used in production design systems for semantic color scales (background, content, border, shadow) with values from 0-10.

Why the restriction is unnecessary

The validation comment states the concern is that border--* could match the bare class border- when using default values. However, this edge case is already handled:

  1. findRoots in candidate.ts (line 887) already rejects empty values: if (root[1] === '') break
  2. The Oxide scanner already extracts double-dash candidates correctly, as confirmed by existing tests: ("items--center", vec!["items--center"])

The candidate parser and scanner both handle this case. The validation was an overcorrection.

Changes

  • Removed the trailing-dash check from isValidFunctionalUtilityName in utilities.ts
  • Updated the existing unit test from ['foo--*', false] to ['foo--*', true]
  • Added an integration test proving @utility border--* compiles correctly with theme values

Test results

All 4121 tests pass across the tailwindcss package, including the new integration test.

The `isValidFunctionalUtilityName` function rejected utility names where
the root ended with a dash after stripping the `-*` suffix. This prevented
valid use cases like `@utility border--*` where the double dash serves as
a visual separator between the CSS property and a value scale.

The stated concern was that a utility like `border--*` could match the
bare class `border-` when using default values. However, the candidate
parser's `findRoots` function already handles this edge case by rejecting
empty values (line 887: `if (root[1] === '') break`). The Oxide scanner
also already extracts double-dash candidates correctly, as confirmed by
existing tests (`items--center`).

This enables design systems that use double-dash naming conventions for
semantic color scales (e.g. `border--0` through `border--10`), where
the double dash cleanly separates the property name from the scale value.
@kirkouimet kirkouimet requested a review from a team as a code owner February 18, 2026 16:44
@andrewdisley
Copy link

Thanks for raising this @kirkouimet - we are experiencing the exact same issue with 4.2.0. Our usage and patterns are as you have described we use it for colour utilities along with some others were we prefer the class='bg bg--token fg fg--token':

@utility bg {
  background-color: var(--bg);
}

@utility bg--* {
  --bg: --value(--colour-*);
}

@utility fg {
  color: var(--fg);

  & a:not(.a-button) {
    color: var(--link-fg);
  }
}

@utility fg--* {
  --fg: --value(--colour-*);
  --link-fg: --value(--colour-* --link);
}

@RobinMalfait RobinMalfait self-assigned this Feb 19, 2026
@RobinMalfait RobinMalfait enabled auto-merge (squash) February 19, 2026 14:35
Copy link
Member

@RobinMalfait RobinMalfait left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, I wish we didn't ship support for this in the first place. But you're right this used to work in previous versions just fine so going to allow it for backwards compatibility.

We might revisit this in Tailwind CSS v5, but we also don't like breaking things so it might work like this forever who knows. For now, going to accept it.

Thanks!

@RobinMalfait RobinMalfait merged commit d15d92c into tailwindlabs:main Feb 19, 2026
8 checks passed
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

No actionable comments were generated in the recent review. 🎉


Walkthrough

This pull request modifies the utility name validation logic in TailwindCSS to permit trailing dashes in functional utility names and double dashes within root names. Changes include updating the validation functions isValidStaticUtilityName and isValidFunctionalUtilityName to accept patterns like foo--* and border--* as valid. Test cases are expanded to verify this behavior, and the CHANGELOG is updated to document this fix for backward compatibility. The modifications relax previous constraints on dash characters in utility naming conventions.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: allowing trailing dashes in functional utility names, which is the core focus of the PR and directly reflects the primary code modification in utilities.ts.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the problem, reasoning, and implementation details of allowing trailing dashes in functional utility names.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kirkouimet
Copy link
Contributor Author

@RobinMalfait thank you, really appreciate the quick review and merge.

Wanted to share some context on why we landed on this pattern. We use semantic color scales (0-10) for theming, where a single class like border--2 resolves to different values in light and dark mode. The double dash separates the CSS property from the scale index, so the theme layer controls the actual color.

The alternative with single dash collides with Tailwind's built-in utilities (border-0 is border-width), and the standard Tailwind approach of border-gray-200 dark:border-gray-700 doubles every color class. With this pattern a dense className string like border border--0 handles both modes in one declaration, and the scale values are immediately scannable.

So the double dash isn't a stylistic choice on our end, it's the minimal syntax that lets semantic theming coexist with Tailwind's existing namespace. Thanks again for keeping it supported.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants