Skip to content

Commit 0f6f7d4

Browse files
authored
Ensure --value(…) is required in functional @utility definitions (#20005)
While working on #19989, I noticed that `--value(…)` inside functional `@utility` definitions is not required right now. That means that the following CSS is valid: ```css @Utility foo-* { color: red; } ``` But this doesn't really makes sense, because this now accepts a value and `foo-a`, `foo-b` and `foo-c` would generate the following CSS: ```css .foo-a { color: red; } .foo-b { color: red; } .foo-c { color: red; } ``` The `a`, `b`, and `c` are not doing anything here apart from making your CSS bigger. So this is very likely an actual bug that you forgot to use `--value(…)`. Additionally, if a `--value(…)` was used, but it didn't resolve anything, then we already properly discared the candidate. ## Test plan 1. Add test to ensure `--value(…)` is required in functional `@utility` definitions 2. Existing tests pass
1 parent 6e2b60e commit 0f6f7d4

3 files changed

Lines changed: 44 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
- Allow multiple `@utility` definitions with the same name but different value types ([#19777](https://github.com/tailwindlabs/tailwindcss/pull/19777))
2828
- Export missing `PluginWithConfig` type from `tailwindcss/plugin` to fix errors when inferring plugin config types ([#19707](https://github.com/tailwindlabs/tailwindcss/pull/19707))
2929
- Ensure `start` and `end` legacy utilities without values do not generate CSS ([#20003](https://github.com/tailwindlabs/tailwindcss/pull/20003))
30+
- Ensure `--value(…)` is required in functional `@utility` definitions ([#20005](https://github.com/tailwindlabs/tailwindcss/pull/20005))
3031

3132
## [4.2.4] - 2026-04-21
3233

packages/tailwindcss/src/utilities.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28870,6 +28870,40 @@ describe('custom utilities', () => {
2887028870
})
2887128871

2887228872
describe('functional utilities', () => {
28873+
test('functional utilities require a `--value(…)`', async () => {
28874+
let input = css`
28875+
@utility tab-* {
28876+
tab-size: 4;
28877+
}
28878+
28879+
@tailwind utilities;
28880+
`
28881+
28882+
expect(await compileCss(input, ['tab', 'tab-foo'])).toEqual('')
28883+
})
28884+
28885+
test('functional utilities must resolve at least one `--value(…)`', async () => {
28886+
let input = css`
28887+
@utility tab-* {
28888+
tab-size: --value(integer);
28889+
}
28890+
28891+
@tailwind utilities;
28892+
`
28893+
28894+
expect(await compileCss(input, ['tab-1', 'tab-2'])).toMatchInlineSnapshot(`
28895+
".tab-1 {
28896+
tab-size: 1;
28897+
}
28898+
28899+
.tab-2 {
28900+
tab-size: 2;
28901+
}"
28902+
`)
28903+
28904+
expect(await compileCss(input, ['tab', 'tab-foo', 'tab-2.5'])).toEqual('')
28905+
})
28906+
2887328907
test('resolving values from `@theme`', async () => {
2887428908
let input = css`
2887528909
@theme reference {

packages/tailwindcss/src/utilities.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6178,14 +6178,14 @@ export function createCssUtility(node: AtRule) {
61786178
let shouldRemoveDeclaration = false
61796179

61806180
let valueAst = ValueParser.parse(node.value)
6181-
walk(valueAst, (valueNode) => {
6182-
if (valueNode.kind !== 'function') return
6181+
walk(valueAst, (fnNode) => {
6182+
if (fnNode.kind !== 'function') return
61836183

61846184
// Value function, e.g.: `--value(integer)`
6185-
if (valueNode.value === '--value') {
6185+
if (fnNode.value === '--value') {
61866186
usedValueFn = true
61876187

6188-
let resolved = resolveValueFunction(value, valueNode, designSystem)
6188+
let resolved = resolveValueFunction(value, fnNode, designSystem)
61896189
if (resolved) {
61906190
resolvedValueFn = true
61916191
if (resolved.ratio) {
@@ -6203,7 +6203,7 @@ export function createCssUtility(node: AtRule) {
62036203
}
62046204

62056205
// Modifier function, e.g.: `--modifier(integer)`
6206-
else if (valueNode.value === '--modifier') {
6206+
else if (fnNode.value === '--modifier') {
62076207
// If there is no modifier present in the candidate, then the
62086208
// declaration can be removed.
62096209
if (modifier === null) {
@@ -6213,7 +6213,7 @@ export function createCssUtility(node: AtRule) {
62136213

62146214
usedModifierFn = true
62156215

6216-
let replacement = resolveValueFunction(modifier, valueNode, designSystem)
6216+
let replacement = resolveValueFunction(modifier, fnNode, designSystem)
62176217
if (replacement) {
62186218
resolvedModifierFn = true
62196219
return WalkAction.ReplaceSkip(replacement.nodes)
@@ -6233,8 +6233,9 @@ export function createCssUtility(node: AtRule) {
62336233
node.value = ValueParser.toCss(valueAst)
62346234
})
62356235

6236-
// Used `--value(…)` but nothing resolved
6237-
if (usedValueFn && !resolvedValueFn) return null
6236+
// Functional CSS utilities require `--value(…)`, and one of those
6237+
// branches must resolve for the candidate to be valid.
6238+
if (!usedValueFn || !resolvedValueFn) return null
62386239

62396240
// Used `--modifier(…)` but nothing resolved
62406241
if (usedModifierFn && !resolvedModifierFn) return null

0 commit comments

Comments
 (0)