fix(utilities): check staticValues before fallback theme keys

When `--spacing-none: 0` is defined in `@theme`, `theme.resolve('none',
['--leading', '--spacing'])` finds `--spacing-none` before the staticValues
fallback, causing `leading-none` to produce `line-height: 0` instead of
`line-height: 1`.

Fix by resolving against the primary theme key first, then checking
staticValues before falling back to secondary theme keys. This ensures
explicit static values like `leading-none` are not shadowed by unrelated
theme namespaces while still allowing `--leading-none` theme overrides.

Fixes #19722

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Van Horn 2026-03-10 00:47:54 -07:00
parent c586bd6a94
commit d04174ff12
2 changed files with 50 additions and 9 deletions

View file

@ -24618,6 +24618,38 @@ test('leading', async () => {
`)
})
test('leading-none is not shadowed by --spacing-none', async () => {
expect(
await compileCss(
css`
@theme {
--spacing-none: 0;
}
@tailwind utilities;
`,
['leading-none'],
),
).toMatchInlineSnapshot(`
"@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-leading: initial;
}
}
}
.leading-none {
--tw-leading: 1;
line-height: 1;
}
@property --tw-leading {
syntax: "*";
inherits: false
}"
`)
})
test('tracking', async () => {
expect(
await compileCss(

View file

@ -414,10 +414,24 @@ export function createUtilities(theme: Theme) {
value = candidate.value.value
dataType = candidate.value.dataType
} else {
value = theme.resolve(
candidate.value.fraction ?? candidate.value.value,
desc.themeKeys ?? [],
)
let resolvedValue = candidate.value.fraction ?? candidate.value.value
// First try resolving against just the primary theme key.
value = theme.resolve(resolvedValue, desc.themeKeys?.slice(0, 1) ?? [])
// If the primary theme key didn't match, check static values before
// falling back to secondary theme keys (like `--spacing`). This
// ensures e.g. `leading-none` produces `line-height: 1` even when
// `--spacing-none` is defined in the theme.
if (value === null && !negative && desc.staticValues && !candidate.modifier) {
let fallback = desc.staticValues[candidate.value.value]
if (fallback) return fallback.map(cloneAstNode)
}
// Fall back to the full set of theme keys.
if (value === null) {
value = theme.resolve(resolvedValue, desc.themeKeys ?? [])
}
// Automatically handle things like `w-1/2` without requiring `1/2` to
// exist as a theme value.
@ -439,11 +453,6 @@ export function createUtilities(theme: Theme) {
value = desc.handleBareValue(candidate.value)
if (!value?.includes('/') && candidate.modifier) return
}
if (value === null && !negative && desc.staticValues && !candidate.modifier) {
let fallback = desc.staticValues[candidate.value.value]
if (fallback) return fallback.map(cloneAstNode)
}
}
// If there is no value, don't generate any rules.