Skip to content

Commit 754bfc8

Browse files
committed
add --default(…) to --value(…) support
This allows us to re-implement functional utilities with a default value in CSS using `@utility`. Used the explicit `--default()` argument of `--value()` for a few reasons. 1. It's explicit about being a falllback value. If you have `@utility foo-*`, then you want to be able to use `foo`, but `foo-bad` should not compile. 2. When `--value(…)` is used in (complex) property values (think a bunch of `calc(…)` expressions), then we don't need a separate property for this. One of the ideas was to have a literal fallback: ```css @Utility tab-* { tab-size: 4; tab-size: --value(number); } ``` For `tab`, this would compile to: ```css .tab { tab-size: 4; } ``` For `tab-123`, this would compile to: ```css .tab { tab-size: 4; tab-size: 123; } ``` Getting rid of the `tab-size: 4` would be an option, but it's a common pattern in real CSS for fallback values (think hex background color, over a more modern `oklch` color). For `tab-foo`, this would compile to: ```css .tab { tab-size: 4; } ``` Which means that we have an infinite amount classes that would result in the same class, which is bad. We could special case this one because the internal `value` would still be `null`, but it might be too confusing. This syntax without the `--default` also means repetition of certain properties. Add `--modifier(…)` to the mix, and there is even more repetition going on. Another option to consider is that the default fallback is just another option in the `--value(…, 4)`, but if a default fallback is a keyword, then there is a chance that this might conflict with actual keywords we interpret.
1 parent 4b5d6a5 commit 754bfc8

2 files changed

Lines changed: 113 additions & 7 deletions

File tree

packages/tailwindcss/src/utilities.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29540,6 +29540,98 @@ describe('custom utilities', () => {
2954029540
`)
2954129541
})
2954229542

29543+
test('functional utilities require `--value(…)`', async () => {
29544+
let input = css`
29545+
@utility tab-* {
29546+
tab-size: 4;
29547+
}
29548+
29549+
@tailwind utilities;
29550+
`
29551+
29552+
expect(await compileCss(input, ['tab'])).toEqual('')
29553+
})
29554+
29555+
test('functional utilities must resolve a `--value(…)`', async () => {
29556+
let input = css`
29557+
@utility tab-* {
29558+
tab-size: --value(integer);
29559+
}
29560+
29561+
@tailwind utilities;
29562+
`
29563+
29564+
expect(await compileCss(input, ['tab-2.5'])).toEqual('')
29565+
})
29566+
29567+
test('functional utilities can use `--default(…)` in `--value(…)`', async () => {
29568+
let input = css`
29569+
@utility tab-* {
29570+
tab-size: --value(integer, --default(4));
29571+
}
29572+
29573+
@tailwind utilities;
29574+
`
29575+
29576+
expect(await compileCss(input, ['tab', 'tab-123'])).toMatchInlineSnapshot(`
29577+
".tab {
29578+
tab-size: 4;
29579+
}
29580+
29581+
.tab-123 {
29582+
tab-size: 123;
29583+
}"
29584+
`)
29585+
29586+
expect(await compileCss(input, ['tab-foo'])).toEqual('')
29587+
})
29588+
29589+
test('functional utilities can use `--default(…)` in complex expressions', async () => {
29590+
let input = css`
29591+
@utility tab-* {
29592+
tab-size: calc(--value(integer, --default(4)) * 2);
29593+
}
29594+
29595+
@tailwind utilities;
29596+
`
29597+
29598+
expect(await compileCss(input, ['tab', 'tab-123'])).toMatchInlineSnapshot(`
29599+
".tab {
29600+
tab-size: 8;
29601+
}
29602+
29603+
.tab-123 {
29604+
tab-size: 246;
29605+
}"
29606+
`)
29607+
29608+
expect(await compileCss(input, ['tab-foo'])).toEqual('')
29609+
})
29610+
29611+
test('functional utilities can use `--default(…)` with `--modifier(…)`', async () => {
29612+
let input = css`
29613+
@utility tab-* {
29614+
tab-size: --value(integer, --default(4));
29615+
line-height: --modifier(integer);
29616+
}
29617+
29618+
@tailwind utilities;
29619+
`
29620+
29621+
expect(await compileCss(input, ['tab', 'tab/25'])).toMatchInlineSnapshot(`
29622+
".tab\\/25 {
29623+
tab-size: 4;
29624+
line-height: 25;
29625+
}
29626+
29627+
.tab {
29628+
tab-size: 4;
29629+
}"
29630+
`)
29631+
29632+
expect(await compileCss(input, ['tab/foo'])).toEqual('')
29633+
})
29634+
2954329635
test('modifiers', async () => {
2954429636
let input = css`
2954529637
@theme reference {

packages/tailwindcss/src/utilities.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5956,6 +5956,8 @@ export function createCssUtility(node: AtRule) {
59565956
// - `--value(number)` resolves a bare value of type number
59575957
// - `--value([number])` resolves an arbitrary value of type number
59585958
// - `--value(--color)` resolves a theme value in the `color` namespace
5959+
// - `--value(--default(4))` resolves to a default value when only the
5960+
// root of the functional utility was used.
59595961
// - `--value(number, [number])` resolves a bare value of type number or an
59605962
// arbitrary value of type number in order.
59615963
//
@@ -6069,7 +6071,7 @@ export function createCssUtility(node: AtRule) {
60696071
arg = arg.replace(/(-\*){2,}/g, '-*')
60706072

60716073
// Ensure trailing `-*` exists if `-*` isn't present yet
6072-
if (arg[0] === '-' && arg[1] === '-' && !arg.includes('-*')) {
6074+
if (arg[0] === '-' && arg[1] === '-' && !arg.includes('(') && !arg.includes('-*')) {
60736075
arg += '-*'
60746076
}
60756077

@@ -6135,9 +6137,8 @@ export function createCssUtility(node: AtRule) {
61356137
let value = candidate.value
61366138
let modifier = candidate.modifier
61376139

6138-
// A value is required for functional utilities, if you want to accept
6139-
// just `tab-size`, you'd have to use a static utility.
6140-
if (value === null) return
6140+
// Functional CSS utilities must resolve at least one `--value(…)`.
6141+
// Use `--default(…)` inside `--value(…)` for the omitted-value case.
61416142

61426143
// Whether `--value(…)` was used
61436144
let usedValueFn = false
@@ -6185,7 +6186,9 @@ export function createCssUtility(node: AtRule) {
61856186
if (valueNode.value === '--value') {
61866187
usedValueFn = true
61876188

6188-
let resolved = resolveValueFunction(value, valueNode, designSystem)
6189+
let resolved = value
6190+
? resolveValueFunction(value, valueNode, designSystem)
6191+
: resolveDefaultValueFunction(valueNode)
61896192
if (resolved) {
61906193
resolvedValueFn = true
61916194
if (resolved.ratio) {
@@ -6233,8 +6236,9 @@ export function createCssUtility(node: AtRule) {
62336236
node.value = ValueParser.toCss(valueAst)
62346237
})
62356238

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

62396243
// Used `--modifier(…)` but nothing resolved
62406244
if (usedModifierFn && !resolvedModifierFn) return null
@@ -6451,6 +6455,16 @@ function resolveValueFunction(
64516455
}
64526456
}
64536457

6458+
function resolveDefaultValueFunction(
6459+
fn: ValueParser.ValueFunctionNode,
6460+
): { nodes: ValueParser.ValueAstNode[]; ratio?: boolean } | undefined {
6461+
for (let arg of fn.nodes) {
6462+
if (arg.kind === 'function' && arg.value === '--default') {
6463+
return { nodes: arg.nodes }
6464+
}
6465+
}
6466+
}
6467+
64546468
function alphaReplacedShadowProperties(
64556469
property: string,
64566470
value: string,

0 commit comments

Comments
 (0)