Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure `@tailwindcss/oxide` falls back to WASM on platforms without native bindings ([#20383](https://github.com/tailwindlabs/tailwindcss/pull/20383))
- Detect classes in Ruby percent literals using angle brackets or custom delimiters (e.g. `%w<flex>`, `%w|flex|`), including in Slim and Haml templates ([#20387](https://github.com/tailwindlabs/tailwindcss/pull/20387))
- Preserve whitespace in `--default(…)` values in custom functional utilities (e.g. `--default(box alphabetic)` no longer becomes `boxalphabetic`) ([#20392](https://github.com/tailwindlabs/tailwindcss/pull/20392))
- Ensure custom named `--spacing-*` theme values (e.g. `--spacing-none`) don't shadow keyword values of spacing utilities like `leading-none` ([#20394](https://github.com/tailwindlabs/tailwindcss/pull/20394))

## [4.3.3] - 2026-07-16

Expand Down
80 changes: 80 additions & 0 deletions packages/tailwindcss/src/utilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25727,6 +25727,86 @@ test('leading', async () => {
}
"
`)

// Custom named --spacing-* values should not shadow the none keyword but remain resolvable by name
expect(
await run(
['leading-none', 'leading-big'],
css`
@theme {
--spacing-none: 0;
--spacing-big: 3rem;
}
@tailwind utilities;
`,
),
).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;
}
}
}

:root, :host {
--spacing-big: 3rem;
}

.leading-big {
--tw-leading: var(--spacing-big);
line-height: var(--spacing-big);
}

.leading-none {
--tw-leading: 1;
line-height: 1;
}

@property --tw-leading {
syntax: "*";
inherits: false
}
"
`)

expect(
await run(
['leading-none'],
css`
@theme {
--leading-none: 1.5;
--spacing-none: 0;
}
@tailwind utilities;
`,
),
).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;
}
}
}

:root, :host {
--leading-none: 1.5;
}

.leading-none {
--tw-leading: var(--leading-none);
line-height: var(--leading-none);
}

@property --tw-leading {
syntax: "*";
inherits: false
}
"
`)
})

test('tracking', async () => {
Expand Down
27 changes: 25 additions & 2 deletions packages/tailwindcss/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ export function createUtilities(theme: Theme) {
supportsNegative?: boolean
supportsFractions?: boolean
themeKeys?: ThemeKey[]
fallbackThemeKeys?: ThemeKey[]
defaultValue?: string | null
staticValues?: Record<string, AstNode[]>
handleBareValue?: (value: NamedUtilityValue) => string | null
Expand Down Expand Up @@ -421,6 +422,19 @@ export function createUtilities(theme: Theme) {
desc.themeKeys ?? [],
)

if (value === null && desc.fallbackThemeKeys) {
// Static keywords like leading-none win over fallback namespaces like --spacing
if (!negative && desc.staticValues && !candidate.modifier) {
let fallback = desc.staticValues[candidate.value.value]
if (fallback) return fallback.map(cloneAstNode)
}

value = theme.resolve(
candidate.value.fraction ?? candidate.value.value,
desc.fallbackThemeKeys,
)
}

// Automatically handle things like `w-1/2` without requiring `1/2` to
// exist as a theme value.
if (value === null && desc.supportsFractions && candidate.value.fraction) {
Expand Down Expand Up @@ -467,7 +481,7 @@ export function createUtilities(theme: Theme) {
suggest(classRoot, () => [
{
supportsNegative: desc.supportsNegative,
valueThemeKeys: desc.themeKeys ?? [],
valueThemeKeys: [...(desc.themeKeys ?? []), ...(desc.fallbackThemeKeys ?? [])],
hasDefaultValue: desc.defaultValue !== undefined && desc.defaultValue !== null,
supportsFractions: desc.supportsFractions,
},
Expand Down Expand Up @@ -543,8 +557,17 @@ export function createUtilities(theme: Theme) {
utilities.static(`-${name}-px`, () => handle('-1px'))
}
utilities.static(`${name}-px`, () => handle('1px'))

// The generic `--spacing` namespace (and everything after it) is resolved
// only after `staticValues`, so custom values like `--spacing-none` don't
// shadow canonical keywords like `leading-none`.
let fallbackIndex = themeKeys.indexOf('--spacing')
let ownThemeKeys = fallbackIndex === -1 ? themeKeys : themeKeys.slice(0, fallbackIndex)
let fallbackThemeKeys = fallbackIndex === -1 ? undefined : themeKeys.slice(fallbackIndex)

functionalUtility(name, {
themeKeys,
themeKeys: ownThemeKeys,
fallbackThemeKeys,
supportsFractions,
supportsNegative,
defaultValue: null,
Expand Down