Do not allow variants to start with - (#18867)

This PR fixes an issue where custom variants with just `-` in the name
were allowed but weren't actually picked up by Oxide so you couldn't use
them anyway.

The reason we allow `-` is for `kebab-style-variants`, which is very
common, but you shouldn't use `-`, `--` or more in a variant name.

It doesn't really solve the issue (#18863), but it fixes the
inconsistencies in that exist today.

Inconsistencies:
|   | `-:flex` | `--:flex` |
| --: | :--: | :--: |
| Oxide | ❌ | ❌ |
| Tailwind Play | ✅ | ❌ |
| Intellisense | ✅ | ✅ |

- Oxide already had the correct rules setup, so this is expected
- Tailwind Play uses Tailwind's core compile step, but it considers
candidates that start with `--` as a CSS variable instead of a utility.
This means that the `--:flex` was considered a CSS variable and skipped
during compilation.
- Intellisense uses the same APIs than Tailwind's core, but it didn't
have the CSS variable check which resulted in the `--:flex` being
"correct".

With this PR, the matrix looks like this now:
|   | `-:flex` | `--:flex` |
| --: | :--: | :--: |
| Oxide | ❌ | ❌ |
| Tailwind Play | ❌ | ❌ |
| Intellisense | ❌ | ❌ |


This should not be considered a breaking change because Oxide didn't
pick up candidates with variants that start with a `-`. CSS for these
candidates was never generated before.

Closes: #18863

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
This commit is contained in:
Robin Malfait 2025-09-03 16:32:18 +02:00 • committed by GitHub
parent 494051ca08
commit 54c3f308e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 25 additions and 4 deletions

View file

@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Hide internal fields from completions in `matchUtilities` ([#18820](https://github.com/tailwindlabs/tailwindcss/pull/18820))
- Ignore `.vercel` folders by default (can be overridden by `@source …` rules) ([#18855](https://github.com/tailwindlabs/tailwindcss/pull/18855))
- Consider variants starting with `@-` to be invalid (e.g. `@-2xl:flex`) ([#18869](https://github.com/tailwindlabs/tailwindcss/pull/18869))
- Do not allow custom variants to start with a `-` ([#18867](https://github.com/tailwindlabs/tailwindcss/pull/18867))
- Upgrade: Migrate `aria` theme keys to `@custom-variant` ([#18815](https://github.com/tailwindlabs/tailwindcss/pull/18815))
- Upgrade: Migrate `data` theme keys to `@custom-variant` ([#18816](https://github.com/tailwindlabs/tailwindcss/pull/18816))
- Upgrade: Migrate `supports` theme keys to `@custom-variant` ([#18817](https://github.com/tailwindlabs/tailwindcss/pull/18817))

View file

@ -119,7 +119,7 @@ export function buildPluginApi({
addVariant(name, variant) {
if (!IS_VALID_VARIANT_NAME.test(name)) {
throw new Error(
`\`addVariant('${name}')\` defines an invalid variant name. Variants should only contain alphanumeric, dashes or underscore characters.`,
`\`addVariant('${name}')\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.`,
)
}

View file

@ -3768,7 +3768,27 @@ describe('@custom-variant', () => {
@custom-variant foo:bar (&:hover, &:focus);
`),
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: \`@custom-variant foo:bar\` defines an invalid variant name. Variants should only contain alphanumeric, dashes or underscore characters.]`,
`[Error: \`@custom-variant foo:bar\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.]`,
)
})
test('@custom-variant cannot contain dashes on its own', () => {
return expect(
compileCss(css`
@custom-variant - (&.dash);
`),
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: \`@custom-variant -\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.]`,
)
})
test('@custom-variant cannot contain multiple dashes on their own', () => {
return expect(
compileCss(css`
@custom-variant --- (&.dashed);
`),
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: \`@custom-variant ---\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.]`,
)
})

View file

@ -356,7 +356,7 @@ async function parseCss(
if (!IS_VALID_VARIANT_NAME.test(name)) {
throw new Error(
`\`@custom-variant ${name}\` defines an invalid variant name. Variants should only contain alphanumeric, dashes or underscore characters.`,
`\`@custom-variant ${name}\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.`,
)
}

View file

@ -18,7 +18,7 @@ import { DefaultMap } from './utils/default-map'
import { isPositiveInteger } from './utils/infer-data-type'
import { segment } from './utils/segment'
export const IS_VALID_VARIANT_NAME = /^@?[a-zA-Z0-9_-]*$/
export const IS_VALID_VARIANT_NAME = /^@?[a-z0-9][a-zA-Z0-9_-]*$/
type VariantFn<T extends Variant['kind']> = (
rule: Rule,