Skip to content

Commit a3316f2

Browse files
authored
Add support for literal values in --value('…') and --modifier('…') (#17304)
This PR adds support for literal values inside the `--value('…')` and `--modifier('…')` functions. This allows you to safelist some known values you want to use: E.g.: ```css @Utility tab-* { tab-size: --value('revert', 'initial'); } ``` This allows you to use `tab-revert` and `tab-initial` for example.
1 parent 4c57d9f commit a3316f2

File tree

4 files changed

+101
-23
lines changed

4 files changed

+101
-23
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- _Experimental_: Add `user-valid` and `user-invalid` variants ([#12370](https://github.com/tailwindlabs/tailwindcss/pull/12370))
2020
- _Experimental_: Add `wrap-anywhere`, `wrap-break-word`, and `wrap-normal` utilities ([#12128](https://github.com/tailwindlabs/tailwindcss/pull/12128))
2121
- _Experimental_: Add `@source inline(…)` ([#17147](https://github.com/tailwindlabs/tailwindcss/pull/17147))
22+
- Add support for literal values in `--value('…')` and `--modifier('…')` ([#17304](https://github.com/tailwindlabs/tailwindcss/pull/17304))
2223

2324
### [4.0.15] - 2025-03-20
2425

packages/tailwindcss/src/intellisense.test.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -479,13 +479,13 @@ test('Custom functional @utility', async () => {
479479
}
480480
481481
@utility tab-* {
482-
tab-size: --value(--tab-size);
482+
tab-size: --value(--tab-size, 'revert', 'initial');
483483
}
484484
485485
@utility example-* {
486486
font-size: --value(--text);
487487
line-height: --value(--text- * --line-height);
488-
line-height: --modifier(--leading);
488+
line-height: --modifier(--leading, 'normal');
489489
}
490490
491491
@utility -negative-* {
@@ -507,6 +507,8 @@ test('Custom functional @utility', async () => {
507507
expect(classNames).toContain('tab-2')
508508
expect(classNames).toContain('tab-4')
509509
expect(classNames).toContain('tab-github')
510+
expect(classNames).toContain('tab-revert')
511+
expect(classNames).toContain('tab-initial')
510512

511513
expect(classNames).not.toContain('-tab-1')
512514
expect(classNames).not.toContain('-tab-2')
@@ -524,7 +526,7 @@ test('Custom functional @utility', async () => {
524526
expect(classNames).not.toContain('--negative-github')
525527

526528
expect(classNames).toContain('example-xs')
527-
expect(classMap.get('example-xs')?.modifiers).toEqual(['foo', 'bar'])
529+
expect(classMap.get('example-xs')?.modifiers).toEqual(['normal', 'foo', 'bar'])
528530
})
529531

530532
test('Theme keys with underscores are suggested with underscores', async () => {

packages/tailwindcss/src/utilities.test.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -17256,6 +17256,23 @@ describe('custom utilities', () => {
1725617256
expect(await compileCss(input, ['tab-foo'])).toEqual('')
1725717257
})
1725817258

17259+
test('resolve literal values', async () => {
17260+
let input = css`
17261+
@utility tab-* {
17262+
tab-size: --value('revert');
17263+
}
17264+
17265+
@tailwind utilities;
17266+
`
17267+
17268+
expect(await compileCss(input, ['tab-revert'])).toMatchInlineSnapshot(`
17269+
".tab-revert {
17270+
tab-size: revert;
17271+
}"
17272+
`)
17273+
expect(await compileCss(input, ['tab-initial'])).toEqual('')
17274+
})
17275+
1725917276
test('resolving bare values with constraints for integer, percentage, and ratio', async () => {
1726017277
let input = css`
1726117278
@utility example-* {
@@ -17720,6 +17737,7 @@ describe('custom utilities', () => {
1772017737
--value: --value(--value, [length]);
1772117738
--modifier: --modifier(--modifier, [length]);
1772217739
--modifier-with-calc: calc(--modifier(--modifier, [length]) * 2);
17740+
--modifier-literals: --modifier('literal', 'literal-2');
1772317741
}
1772417742
1772517743
@tailwind utilities;
@@ -17731,6 +17749,8 @@ describe('custom utilities', () => {
1773117749
'example-sm/7',
1773217750
'example-[12px]',
1773317751
'example-[12px]/[16px]',
17752+
'example-sm/literal',
17753+
'example-sm/literal-2',
1773417754
]),
1773517755
).toMatchInlineSnapshot(`
1773617756
".example-\\[12px\\]\\/\\[16px\\] {
@@ -17745,6 +17765,16 @@ describe('custom utilities', () => {
1774517765
--modifier-with-calc: calc(var(--modifier-7, 28px) * 2);
1774617766
}
1774717767
17768+
.example-sm\\/literal {
17769+
--value: var(--value-sm, 14px);
17770+
--modifier-literals: literal;
17771+
}
17772+
17773+
.example-sm\\/literal-2 {
17774+
--value: var(--value-sm, 14px);
17775+
--modifier-literals: literal-2;
17776+
}
17777+
1774817778
.example-\\[12px\\] {
1774917779
--value: 12px;
1775017780
}
@@ -17754,7 +17784,12 @@ describe('custom utilities', () => {
1775417784
}"
1775517785
`)
1775617786
expect(
17757-
await compileCss(input, ['example-foo', 'example-foo/[12px]', 'example-foo/12']),
17787+
await compileCss(input, [
17788+
'example-foo',
17789+
'example-foo/[12px]',
17790+
'example-foo/12',
17791+
'example-sm/unknown-literal',
17792+
]),
1775817793
).toEqual('')
1775917794
})
1776017795

packages/tailwindcss/src/utilities.ts

+59-19
Original file line numberDiff line numberDiff line change
@@ -4706,6 +4706,7 @@ export function createCssUtility(node: AtRule) {
47064706
if (IS_VALID_FUNCTIONAL_UTILITY_NAME.test(name)) {
47074707
// API:
47084708
//
4709+
// - `--value('literal')` resolves a literal named value
47094710
// - `--value(number)` resolves a bare value of type number
47104711
// - `--value([number])` resolves an arbitrary value of type number
47114712
// - `--value(--color)` resolves a theme value in the `color` namespace
@@ -4731,7 +4732,10 @@ export function createCssUtility(node: AtRule) {
47314732

47324733
return (designSystem: DesignSystem) => {
47334734
let valueThemeKeys = new Set<`--${string}`>()
4735+
let valueLiterals = new Set<string>()
4736+
47344737
let modifierThemeKeys = new Set<`--${string}`>()
4738+
let modifierLiterals = new Set<string>()
47354739

47364740
// Pre-process the AST to make it easier to work with.
47374741
//
@@ -4747,12 +4751,12 @@ export function createCssUtility(node: AtRule) {
47474751

47484752
// Required manipulations:
47494753
//
4750-
// - `--value(--spacing)` -> `--value(--spacing-*)`
4751-
// - `--value(--spacing- *)` -> `--value(--spacing-*)`
4752-
// - `--value(--text- * --line-height)` -> `--value(--text-*--line-height)`
4753-
// - `--value(--text --line-height)` -> `--value(--text-*--line-height)`
4754-
// - `--value(--text-\\* --line-height)` -> `--value(--text-*--line-height)`
4755-
// - `--value([ *])` -> `--value([*])`
4754+
// - `--value(--spacing)` -> `--value(--spacing-*)`
4755+
// - `--value(--spacing- *)` -> `--value(--spacing-*)`
4756+
// - `--value(--text- * --line-height)` -> `--value(--text-*--line-height)`
4757+
// - `--value(--text --line-height)` -> `--value(--text-*--line-height)`
4758+
// - `--value(--text-\\* --line-height)` -> `--value(--text-*--line-height)`
4759+
// - `--value([ *])` -> `--value([*])`
47564760
//
47574761
// Once Prettier / Biome handle these better (e.g.: not crashing without
47584762
// `\\*` or not inserting whitespace) then most of these can go away.
@@ -4783,9 +4787,25 @@ export function createCssUtility(node: AtRule) {
47834787
}
47844788
fn.nodes = ValueParser.parse(args.join(','))
47854789

4786-
// Track the theme keys for suggestions
4790+
// Track information for suggestions
47874791
for (let node of fn.nodes) {
4788-
if (node.kind === 'word' && node.value[0] === '-' && node.value[1] === '-') {
4792+
// Track literal values
4793+
if (
4794+
node.kind === 'word' &&
4795+
(node.value[0] === '"' || node.value[0] === "'") &&
4796+
node.value[0] === node.value[node.value.length - 1]
4797+
) {
4798+
let value = node.value.slice(1, -1)
4799+
4800+
if (fn.value === '--value') {
4801+
valueLiterals.add(value)
4802+
} else if (fn.value === '--modifier') {
4803+
modifierLiterals.add(value)
4804+
}
4805+
}
4806+
4807+
// Track theme keys
4808+
else if (node.kind === 'word' && node.value[0] === '-' && node.value[1] === '-') {
47894809
let value = node.value.replace(/-\*.*$/g, '') as `--${string}`
47904810

47914811
if (fn.value === '--value') {
@@ -4929,16 +4949,23 @@ export function createCssUtility(node: AtRule) {
49294949
})
49304950

49314951
designSystem.utilities.suggest(name.slice(0, -2), () => {
4932-
return [
4933-
{
4934-
values: designSystem.theme
4935-
.keysInNamespaces(valueThemeKeys)
4936-
.map((x) => x.replaceAll('_', '.')),
4937-
modifiers: designSystem.theme
4938-
.keysInNamespaces(modifierThemeKeys)
4939-
.map((x) => x.replaceAll('_', '.')),
4940-
},
4941-
] satisfies SuggestionGroup[]
4952+
let values = []
4953+
for (let value of valueLiterals) {
4954+
values.push(value)
4955+
}
4956+
for (let value of designSystem.theme.keysInNamespaces(valueThemeKeys)) {
4957+
values.push(value)
4958+
}
4959+
4960+
let modifiers = []
4961+
for (let modifier of modifierLiterals) {
4962+
modifiers.push(modifier)
4963+
}
4964+
for (let value of designSystem.theme.keysInNamespaces(modifierThemeKeys)) {
4965+
modifiers.push(value)
4966+
}
4967+
4968+
return [{ values, modifiers }] satisfies SuggestionGroup[]
49424969
})
49434970
}
49444971
}
@@ -4961,8 +4988,21 @@ function resolveValueFunction(
49614988
designSystem: DesignSystem,
49624989
): { nodes: ValueParser.ValueAstNode[]; ratio?: boolean } | undefined {
49634990
for (let arg of fn.nodes) {
4964-
// Resolving theme value, e.g.: `--value(--color)`
4991+
// Resolve literal value, e.g.: `--modifier('closest-side')`
49654992
if (
4993+
value.kind === 'named' &&
4994+
arg.kind === 'word' &&
4995+
// Should be wreapped in quotes
4996+
(arg.value[0] === "'" || arg.value[0] === '"') &&
4997+
arg.value[arg.value.length - 1] === arg.value[0] &&
4998+
// Values should match
4999+
arg.value.slice(1, -1) === value.value
5000+
) {
5001+
return { nodes: ValueParser.parse(value.value) }
5002+
}
5003+
5004+
// Resolving theme value, e.g.: `--value(--color)`
5005+
else if (
49665006
value.kind === 'named' &&
49675007
arg.kind === 'word' &&
49685008
arg.value[0] === '-' &&

0 commit comments

Comments
 (0)