diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f7d9be27aa5..b8a11dcb3d5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure that file system watchers created when using the CLI are always cleaned up ([#18905](https://github.com/tailwindlabs/tailwindcss/pull/18905)) - Do not generate `grid-column` utilities when configuring `grid-column-start` or `grid-column-end` ([#18907](https://github.com/tailwindlabs/tailwindcss/pull/18907)) - Do not generate `grid-row` utilities when configuring `grid-row-start` or `grid-row-end` ([#18907](https://github.com/tailwindlabs/tailwindcss/pull/18907)) +- Prevent duplicate CSS when overwriting a static utility with a theme key ([#18056](https://github.com/tailwindlabs/tailwindcss/pull/18056)) ## [4.1.13] - 2025-09-03 diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 5a1fe7596014..c7615708af46 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -1054,6 +1054,26 @@ test('z-index', async () => { '-z-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --z-index-auto: 42; + } + @tailwind utilities; + `, + ['z-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --z-index-auto: 42; + } + + .z-auto { + z-index: var(--z-index-auto); + }" + `) }) test('order', async () => { @@ -1114,6 +1134,46 @@ test('order', async () => { 'order-none/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --order-first: 1; + } + @tailwind utilities; + `, + ['order-first'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --order-first: 1; + } + + .order-first { + order: var(--order-first); + }" + `) + + expect( + await compileCss( + css` + @theme { + --order-last: -1; + } + @tailwind utilities; + `, + ['order-last'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --order-last: -1; + } + + .order-last { + order: var(--order-last); + }" + `) }) test('col', async () => { @@ -1176,6 +1236,26 @@ test('col', async () => { 'col-span-[var(--my-variable)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --grid-column-auto: 5; + } + @tailwind utilities; + `, + ['col-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-column-auto: 5; + } + + .col-auto { + grid-column: var(--grid-column-auto); + }" + `) }) test('col-start', async () => { @@ -1237,6 +1317,26 @@ test('col-start', async () => { '-col-start-4/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --grid-column-start-auto: 7; + } + @tailwind utilities; + `, + ['col-start-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-column-start-auto: 7; + } + + .col-start-auto { + grid-column-start: var(--grid-column-start-auto); + }" + `) }) test('col-end', async () => { @@ -1291,6 +1391,26 @@ test('col-end', async () => { '-col-end-4/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --grid-column-end-auto: 3; + } + @tailwind utilities; + `, + ['col-end-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-column-end-auto: 3; + } + + .col-end-auto { + grid-column-end: var(--grid-column-end-auto); + }" + `) }) test('row', async () => { @@ -1353,6 +1473,26 @@ test('row', async () => { 'row-span-[var(--my-variable)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --grid-row-auto: 9; + } + @tailwind utilities; + `, + ['row-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-row-auto: 9; + } + + .row-auto { + grid-row: var(--grid-row-auto); + }" + `) }) test('row-start', async () => { @@ -1414,6 +1554,26 @@ test('row-start', async () => { '-row-start-4/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --grid-row-start-auto: 11; + } + @tailwind utilities; + `, + ['row-start-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-row-start-auto: 11; + } + + .row-start-auto { + grid-row-start: var(--grid-row-start-auto); + }" + `) }) test('row-end', async () => { @@ -1468,6 +1628,26 @@ test('row-end', async () => { '-row-end-4/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --grid-row-end-auto: 13; + } + @tailwind utilities; + `, + ['row-end-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-row-end-auto: 13; + } + + .row-end-auto { + grid-row-end: var(--grid-row-end-auto); + }" + `) }) test('float', async () => { @@ -2422,6 +2602,29 @@ test('line-clamp', async () => { 'line-clamp-none/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --line-clamp-none: 0; + } + @tailwind utilities; + `, + ['line-clamp-none'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --line-clamp-none: 0; + } + + .line-clamp-none { + -webkit-line-clamp: var(--line-clamp-none); + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden; + }" + `) }) test('display', async () => { @@ -3980,6 +4183,26 @@ test('origin', async () => { 'origin-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --transform-origin-top: 10px 20px; + } + @tailwind utilities; + `, + ['origin-top'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --transform-origin-top: 10px 20px; + } + + .origin-top { + transform-origin: var(--transform-origin-top); + }" + `) }) test('perspective-origin', async () => { @@ -4059,6 +4282,27 @@ test('perspective-origin', async () => { 'perspective-origin-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --perspective-origin-top: 10px 20px; + } + @tailwind utilities; + `, + ['perspective-origin-top'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --perspective-origin-top: 10px 20px; + } + + .perspective-origin-top { + perspective-origin: var(--perspective-origin-top); + perspective: var(--perspective-origin-top); + }" + `) }) test('translate', async () => { @@ -5560,6 +5804,26 @@ test('perspective', async () => { 'perspective-[456px]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --perspective-none: 400px; + } + @tailwind utilities; + `, + ['perspective-none'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --perspective-none: 400px; + } + + .perspective-none { + perspective: var(--perspective-none); + }" + `) }) test('cursor', async () => { @@ -6937,6 +7201,26 @@ test('list', async () => { 'list-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --list-style-type-none: disc; + } + @tailwind utilities; + `, + ['list-none'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --list-style-type-none: disc; + } + + .list-none { + list-style-type: var(--list-style-type-none); + }" + `) }) test('list-image', async () => { @@ -6958,6 +7242,26 @@ test('list-image', async () => { 'list-image-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --list-style-image-none: url(../foo.png); + } + @tailwind utilities; + `, + ['list-image-none'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --list-style-image-none: url("../foo.png"); + } + + .list-image-none { + list-style-image: var(--list-style-image-none); + }" + `) }) test('appearance', async () => { @@ -7099,6 +7403,26 @@ test('columns', async () => { 'columns-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --columns-auto: 3; + } + @tailwind utilities; + `, + ['columns-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --columns-auto: 3; + } + + .columns-auto { + columns: var(--columns-auto); + }" + `) }) test('break-before', async () => { @@ -7319,9 +7643,29 @@ test('auto-cols', async () => { 'auto-cols-[2fr]/foo', ]), ).toEqual('') -}) -test('grid-flow', async () => { + expect( + await compileCss( + css` + @theme { + --grid-auto-columns-auto: 2fr; + } + @tailwind utilities; + `, + ['auto-cols-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-auto-columns-auto: 2fr; + } + + .auto-cols-auto { + grid-auto-columns: var(--grid-auto-columns-auto); + }" + `) +}) + +test('grid-flow', async () => { expect( await run([ 'grid-flow-row', @@ -7410,6 +7754,26 @@ test('auto-rows', async () => { 'auto-rows-[2fr]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --grid-auto-rows-auto: 2fr; + } + @tailwind utilities; + `, + ['auto-rows-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-auto-rows-auto: 2fr; + } + + .auto-rows-auto { + grid-auto-rows: var(--grid-auto-rows-auto); + }" + `) }) test('grid-cols', async () => { @@ -7459,6 +7823,26 @@ test('grid-cols', async () => { 'grid-cols-[123]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --grid-template-columns-none: 200px 1fr; + } + @tailwind utilities; + `, + ['grid-cols-none'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-template-columns-none: 200px 1fr; + } + + .grid-cols-none { + grid-template-columns: var(--grid-template-columns-none); + }" + `) }) test('grid-rows', async () => { @@ -7508,6 +7892,26 @@ test('grid-rows', async () => { 'grid-rows-[123]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --grid-template-rows-none: 200px 1fr; + } + @tailwind utilities; + `, + ['grid-rows-none'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --grid-template-rows-none: 200px 1fr; + } + + .grid-rows-none { + grid-template-rows: var(--grid-template-rows-none); + }" + `) }) test('flex-direction', async () => { @@ -9770,6 +10174,25 @@ test('rounded', async () => { border-radius: var(--radius-sm); }" `) + expect( + await compileCss( + css` + @theme { + --radius-full: 99999px; + } + @tailwind utilities; + `, + ['rounded-full'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --radius-full: 99999px; + } + + .rounded-full { + border-radius: var(--radius-full); + }" + `) expect( await run([ '-rounded', @@ -9945,15 +10368,11 @@ test('rounded-t', async () => { } .rounded-t-full { - border-top-left-radius: 3.40282e38px; - border-top-right-radius: 3.40282e38px; border-top-left-radius: var(--radius-full); border-top-right-radius: var(--radius-full); } .rounded-t-none { - border-top-left-radius: 0; - border-top-right-radius: 0; border-top-left-radius: var(--radius-none); border-top-right-radius: var(--radius-none); } @@ -10012,15 +10431,11 @@ test('rounded-r', async () => { } .rounded-r-full { - border-top-right-radius: 3.40282e38px; - border-bottom-right-radius: 3.40282e38px; border-top-right-radius: var(--radius-full); border-bottom-right-radius: var(--radius-full); } .rounded-r-none { - border-top-right-radius: 0; - border-bottom-right-radius: 0; border-top-right-radius: var(--radius-none); border-bottom-right-radius: var(--radius-none); } @@ -10079,15 +10494,11 @@ test('rounded-b', async () => { } .rounded-b-full { - border-bottom-right-radius: 3.40282e38px; - border-bottom-left-radius: 3.40282e38px; border-bottom-right-radius: var(--radius-full); border-bottom-left-radius: var(--radius-full); } .rounded-b-none { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; border-bottom-right-radius: var(--radius-none); border-bottom-left-radius: var(--radius-none); } @@ -10146,15 +10557,11 @@ test('rounded-l', async () => { } .rounded-l-full { - border-top-left-radius: 3.40282e38px; - border-bottom-left-radius: 3.40282e38px; border-top-left-radius: var(--radius-full); border-bottom-left-radius: var(--radius-full); } .rounded-l-none { - border-top-left-radius: 0; - border-bottom-left-radius: 0; border-top-left-radius: var(--radius-none); border-bottom-left-radius: var(--radius-none); } @@ -10443,12 +10850,10 @@ test('rounded-tl', async () => { } .rounded-tl-full { - border-top-left-radius: 3.40282e38px; border-top-left-radius: var(--radius-full); } .rounded-tl-none { - border-top-left-radius: 0; border-top-left-radius: var(--radius-none); } @@ -10503,12 +10908,10 @@ test('rounded-tr', async () => { } .rounded-tr-full { - border-top-right-radius: 3.40282e38px; border-top-right-radius: var(--radius-full); } .rounded-tr-none { - border-top-right-radius: 0; border-top-right-radius: var(--radius-none); } @@ -10563,12 +10966,10 @@ test('rounded-br', async () => { } .rounded-br-full { - border-bottom-right-radius: 3.40282e38px; border-bottom-right-radius: var(--radius-full); } .rounded-br-none { - border-bottom-right-radius: 0; border-bottom-right-radius: var(--radius-none); } @@ -10623,12 +11024,10 @@ test('rounded-bl', async () => { } .rounded-bl-full { - border-bottom-left-radius: 3.40282e38px; border-bottom-left-radius: var(--radius-full); } .rounded-bl-none { - border-bottom-left-radius: 0; border-bottom-left-radius: var(--radius-none); } @@ -20025,6 +20424,26 @@ test('object', async () => { 'object-top/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --object-position-center: top left; + } + @tailwind utilities; + `, + ['object-center'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --object-position-center: top left; + } + + .object-center { + object-position: var(--object-position-center); + }" + `) }) test('p', async () => { @@ -21327,6 +21746,26 @@ test('animate', async () => { 'animate-not-found/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --animate-none: bounce 1s infinite; + } + @tailwind utilities; + `, + ['animate-none'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --animate-none: bounce 1s infinite; + } + + .animate-none { + animation: var(--animate-none); + }" + `) }) test('filter', async () => { @@ -21756,6 +22195,113 @@ test('filter', async () => { 'sepia-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --blur-none: 2px; + } + @tailwind utilities; + `, + ['blur-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-blur: initial; + --tw-brightness: initial; + --tw-contrast: initial; + --tw-grayscale: initial; + --tw-hue-rotate: initial; + --tw-invert: initial; + --tw-opacity: initial; + --tw-saturate: initial; + --tw-sepia: initial; + --tw-drop-shadow: initial; + --tw-drop-shadow-color: initial; + --tw-drop-shadow-alpha: 100%; + --tw-drop-shadow-size: initial; + } + } + } + + :root, :host { + --blur-none: 2px; + } + + .blur-none { + --tw-blur: blur(var(--blur-none)); + filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, ); + } + + @property --tw-blur { + syntax: "*"; + inherits: false + } + + @property --tw-brightness { + syntax: "*"; + inherits: false + } + + @property --tw-contrast { + syntax: "*"; + inherits: false + } + + @property --tw-grayscale { + syntax: "*"; + inherits: false + } + + @property --tw-hue-rotate { + syntax: "*"; + inherits: false + } + + @property --tw-invert { + syntax: "*"; + inherits: false + } + + @property --tw-opacity { + syntax: "*"; + inherits: false + } + + @property --tw-saturate { + syntax: "*"; + inherits: false + } + + @property --tw-sepia { + syntax: "*"; + inherits: false + } + + @property --tw-drop-shadow { + syntax: "*"; + inherits: false + } + + @property --tw-drop-shadow-color { + syntax: "*"; + inherits: false + } + + @property --tw-drop-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; + } + + @property --tw-drop-shadow-size { + syntax: "*"; + inherits: false + }" + `) }) test('backdrop-filter', async () => { @@ -22136,6 +22682,89 @@ test('backdrop-filter', async () => { 'backdrop-sepia-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --backdrop-blur-none: 2px; + } + @tailwind utilities; + `, + ['backdrop-blur-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-backdrop-blur: initial; + --tw-backdrop-brightness: initial; + --tw-backdrop-contrast: initial; + --tw-backdrop-grayscale: initial; + --tw-backdrop-hue-rotate: initial; + --tw-backdrop-invert: initial; + --tw-backdrop-opacity: initial; + --tw-backdrop-saturate: initial; + --tw-backdrop-sepia: initial; + } + } + } + + :root, :host { + --backdrop-blur-none: 2px; + } + + .backdrop-blur-none { + --tw-backdrop-blur: blur(var(--backdrop-blur-none)); + -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); + backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); + } + + @property --tw-backdrop-blur { + syntax: "*"; + inherits: false + } + + @property --tw-backdrop-brightness { + syntax: "*"; + inherits: false + } + + @property --tw-backdrop-contrast { + syntax: "*"; + inherits: false + } + + @property --tw-backdrop-grayscale { + syntax: "*"; + inherits: false + } + + @property --tw-backdrop-hue-rotate { + syntax: "*"; + inherits: false + } + + @property --tw-backdrop-invert { + syntax: "*"; + inherits: false + } + + @property --tw-backdrop-opacity { + syntax: "*"; + inherits: false + } + + @property --tw-backdrop-saturate { + syntax: "*"; + inherits: false + } + + @property --tw-backdrop-sepia { + syntax: "*"; + inherits: false + }" + `) }) test('transition', async () => { @@ -22195,9 +22824,6 @@ test('transition', async () => { } .transition-opacity { - transition-property: opacity; - transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); - transition-duration: var(--tw-duration, var(--default-transition-duration)); transition-property: var(--transition-property-opacity); transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); @@ -22283,6 +22909,28 @@ test('transition', async () => { 'transition-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --transition-property-colors: transform; + } + @tailwind utilities; + `, + ['transition-colors'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --transition-property-colors: transform; + } + + .transition-colors { + transition-property: var(--transition-property-colors); + transition-timing-function: var(--tw-ease, ease); + transition-duration: var(--tw-duration, 0s); + }" + `) }) test('transition-behavior', async () => { @@ -22426,6 +23074,40 @@ test('ease', async () => { 'ease-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --ease-linear: steps(4); + } + @tailwind utilities; + `, + ['ease-linear'], + ), + ).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-ease: initial; + } + } + } + + :root, :host { + --ease-linear: steps(4); + } + + .ease-linear { + --tw-ease: var(--ease-linear); + transition-timing-function: var(--ease-linear); + } + + @property --tw-ease { + syntax: "*"; + inherits: false + }" + `) }) test('will-change', async () => { @@ -22683,6 +23365,40 @@ test('leading', async () => { 'leading-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --leading-none: 2; + } + @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; + } + } + } + + :root, :host { + --leading-none: 2; + } + + .leading-none { + --tw-leading: var(--leading-none); + line-height: var(--leading-none); + } + + @property --tw-leading { + syntax: "*"; + inherits: false + }" + `) }) test('tracking', async () => { @@ -23410,6 +24126,26 @@ test('underline-offset', async () => { '-underline-offset-[var(--value)]/foo', ]), ).toEqual('') + + expect( + await compileCss( + css` + @theme { + --text-underline-offset-auto: 4px; + } + @tailwind utilities; + `, + ['underline-offset-auto'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --text-underline-offset-auto: 4px; + } + + .underline-offset-auto { + text-underline-offset: var(--text-underline-offset-auto); + }" + `) }) test('text', async () => { diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 2cc1fe37e89a..efe7b3dc65ad 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -375,6 +375,7 @@ export function createUtilities(theme: Theme) { supportsFractions?: boolean themeKeys?: ThemeKey[] defaultValue?: string | null + staticValues?: Record handleBareValue?: (value: NamedUtilityValue) => string | null handleNegativeBareValue?: (value: NamedUtilityValue) => string | null handle: (value: string, dataType: string | null) => AstNode[] | undefined @@ -431,6 +432,11 @@ 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 + } } // If there is no value, don't generate any rules. @@ -454,6 +460,13 @@ export function createUtilities(theme: Theme) { supportsFractions: desc.supportsFractions, }, ]) + + // Also suggest any staticValues automatically so callers don't need to + // manually add suggestion groups for e.g. `auto`, `none`, `full`, etc. + if (desc.staticValues && Object.keys(desc.staticValues).length > 0) { + let values = Object.keys(desc.staticValues) + suggest(classRoot, () => [{ values }]) + } } type ColorUtilityDescription = { @@ -506,9 +519,11 @@ export function createUtilities(theme: Theme) { { supportsNegative = false, supportsFractions = false, + staticValues, }: { supportsNegative?: boolean supportsFractions?: boolean + staticValues?: Record } = {}, ) { if (supportsNegative) { @@ -535,6 +550,7 @@ export function createUtilities(theme: Theme) { return `calc(${multiplier} * -${value})` }, handle, + staticValues, }) suggest(name, () => [ @@ -629,7 +645,6 @@ export function createUtilities(theme: Theme) { /** * @css `z-index` */ - staticUtility('z-auto', [['z-index', 'auto']]) functionalUtility('z', { supportsNegative: true, handleBareValue: ({ value }) => { @@ -638,6 +653,9 @@ export function createUtilities(theme: Theme) { }, themeKeys: ['--z-index'], handle: (value) => [decl('z-index', value)], + staticValues: { + auto: [decl('z-index', 'auto')], + }, }) suggest('z', () => [ @@ -651,8 +669,6 @@ export function createUtilities(theme: Theme) { /** * @css `order` */ - staticUtility('order-first', [['order', '-9999']]) - staticUtility('order-last', [['order', '9999']]) functionalUtility('order', { supportsNegative: true, handleBareValue: ({ value }) => { @@ -661,6 +677,10 @@ export function createUtilities(theme: Theme) { }, themeKeys: ['--order'], handle: (value) => [decl('order', value)], + staticValues: { + first: [decl('order', '-9999')], + last: [decl('order', '9999')], + }, }) suggest('order', () => [ @@ -674,7 +694,6 @@ export function createUtilities(theme: Theme) { /** * @css `grid-column` */ - staticUtility('col-auto', [['grid-column', 'auto']]) functionalUtility('col', { supportsNegative: true, handleBareValue: ({ value }) => { @@ -683,20 +702,25 @@ export function createUtilities(theme: Theme) { }, themeKeys: ['--grid-column'], handle: (value) => [decl('grid-column', value)], + staticValues: { + auto: [decl('grid-column', 'auto')], + }, }) - staticUtility('col-span-full', [['grid-column', '1 / -1']]) + functionalUtility('col-span', { handleBareValue: ({ value }) => { if (!isPositiveInteger(value)) return null return value }, handle: (value) => [decl('grid-column', `span ${value} / span ${value}`)], + staticValues: { + full: [decl('grid-column', '1 / -1')], + }, }) /** * @css `grid-column-start` */ - staticUtility('col-start-auto', [['grid-column-start', 'auto']]) functionalUtility('col-start', { supportsNegative: true, handleBareValue: ({ value }) => { @@ -705,12 +729,14 @@ export function createUtilities(theme: Theme) { }, themeKeys: ['--grid-column-start'], handle: (value) => [decl('grid-column-start', value)], + staticValues: { + auto: [decl('grid-column-start', 'auto')], + }, }) /** * @css `grid-column-end` */ - staticUtility('col-end-auto', [['grid-column-end', 'auto']]) functionalUtility('col-end', { supportsNegative: true, handleBareValue: ({ value }) => { @@ -719,6 +745,9 @@ export function createUtilities(theme: Theme) { }, themeKeys: ['--grid-column-end'], handle: (value) => [decl('grid-column-end', value)], + staticValues: { + auto: [decl('grid-column-end', 'auto')], + }, }) suggest('col-span', () => [ @@ -747,7 +776,6 @@ export function createUtilities(theme: Theme) { /** * @css `grid-row` */ - staticUtility('row-auto', [['grid-row', 'auto']]) functionalUtility('row', { supportsNegative: true, handleBareValue: ({ value }) => { @@ -756,8 +784,11 @@ export function createUtilities(theme: Theme) { }, themeKeys: ['--grid-row'], handle: (value) => [decl('grid-row', value)], + staticValues: { + auto: [decl('grid-row', 'auto')], + }, }) - staticUtility('row-span-full', [['grid-row', '1 / -1']]) + functionalUtility('row-span', { themeKeys: [], handleBareValue: ({ value }) => { @@ -765,12 +796,14 @@ export function createUtilities(theme: Theme) { return value }, handle: (value) => [decl('grid-row', `span ${value} / span ${value}`)], + staticValues: { + full: [decl('grid-row', '1 / -1')], + }, }) /** * @css `grid-row-start` */ - staticUtility('row-start-auto', [['grid-row-start', 'auto']]) functionalUtility('row-start', { supportsNegative: true, handleBareValue: ({ value }) => { @@ -779,12 +812,14 @@ export function createUtilities(theme: Theme) { }, themeKeys: ['--grid-row-start'], handle: (value) => [decl('grid-row-start', value)], + staticValues: { + auto: [decl('grid-row-start', 'auto')], + }, }) /** * @css `grid-row-end` */ - staticUtility('row-end-auto', [['grid-row-end', 'auto']]) functionalUtility('row-end', { supportsNegative: true, handleBareValue: ({ value }) => { @@ -793,6 +828,9 @@ export function createUtilities(theme: Theme) { }, themeKeys: ['--grid-row-end'], handle: (value) => [decl('grid-row-end', value)], + staticValues: { + auto: [decl('grid-row-end', 'auto')], + }, }) suggest('row-span', () => [ @@ -866,12 +904,6 @@ export function createUtilities(theme: Theme) { /** * @css `line-clamp` */ - staticUtility('line-clamp-none', [ - ['overflow', 'visible'], - ['display', 'block'], - ['-webkit-box-orient', 'horizontal'], - ['-webkit-line-clamp', 'unset'], - ]) functionalUtility('line-clamp', { themeKeys: ['--line-clamp'], handleBareValue: ({ value }) => { @@ -884,6 +916,14 @@ export function createUtilities(theme: Theme) { decl('-webkit-box-orient', 'vertical'), decl('-webkit-line-clamp', value), ], + staticValues: { + none: [ + decl('overflow', 'visible'), + decl('display', 'block'), + decl('-webkit-box-orient', 'horizontal'), + decl('-webkit-line-clamp', 'unset'), + ], + }, }) suggest('line-clamp', () => [ @@ -928,8 +968,6 @@ export function createUtilities(theme: Theme) { /** * @css `aspect-ratio` */ - staticUtility('aspect-auto', [['aspect-ratio', 'auto']]) - staticUtility('aspect-square', [['aspect-ratio', '1 / 1']]) functionalUtility('aspect', { themeKeys: ['--aspect'], handleBareValue: ({ fraction }) => { @@ -939,6 +977,10 @@ export function createUtilities(theme: Theme) { return fraction }, handle: (value) => [decl('aspect-ratio', value)], + staticValues: { + auto: [decl('aspect-ratio', 'auto')], + square: [decl('aspect-ratio', '1 / 1')], + }, }) /** @@ -1171,41 +1213,47 @@ export function createUtilities(theme: Theme) { /** * @css `transform-origin` */ - staticUtility('origin-center', [['transform-origin', 'center']]) - staticUtility('origin-top', [['transform-origin', 'top']]) - staticUtility('origin-top-right', [['transform-origin', 'top right']]) - staticUtility('origin-right', [['transform-origin', 'right']]) - staticUtility('origin-bottom-right', [['transform-origin', 'bottom right']]) - staticUtility('origin-bottom', [['transform-origin', 'bottom']]) - staticUtility('origin-bottom-left', [['transform-origin', 'bottom left']]) - staticUtility('origin-left', [['transform-origin', 'left']]) - staticUtility('origin-top-left', [['transform-origin', 'top left']]) functionalUtility('origin', { themeKeys: ['--transform-origin'], handle: (value) => [decl('transform-origin', value)], + staticValues: { + center: [decl('transform-origin', 'center')], + top: [decl('transform-origin', 'top')], + 'top-right': [decl('transform-origin', '100% 0')], + right: [decl('transform-origin', '100%')], + 'bottom-right': [decl('transform-origin', '100% 100%')], + bottom: [decl('transform-origin', 'bottom')], + 'bottom-left': [decl('transform-origin', '0 100%')], + left: [decl('transform-origin', '0')], + 'top-left': [decl('transform-origin', '0 0')], + }, }) - staticUtility('perspective-origin-center', [['perspective-origin', 'center']]) - staticUtility('perspective-origin-top', [['perspective-origin', 'top']]) - staticUtility('perspective-origin-top-right', [['perspective-origin', 'top right']]) - staticUtility('perspective-origin-right', [['perspective-origin', 'right']]) - staticUtility('perspective-origin-bottom-right', [['perspective-origin', 'bottom right']]) - staticUtility('perspective-origin-bottom', [['perspective-origin', 'bottom']]) - staticUtility('perspective-origin-bottom-left', [['perspective-origin', 'bottom left']]) - staticUtility('perspective-origin-left', [['perspective-origin', 'left']]) - staticUtility('perspective-origin-top-left', [['perspective-origin', 'top left']]) functionalUtility('perspective-origin', { themeKeys: ['--perspective-origin'], handle: (value) => [decl('perspective-origin', value)], + staticValues: { + center: [decl('perspective-origin', 'center')], + top: [decl('perspective-origin', 'top')], + 'top-right': [decl('perspective-origin', '100% 0')], + right: [decl('perspective-origin', '100%')], + 'bottom-right': [decl('perspective-origin', '100% 100%')], + bottom: [decl('perspective-origin', 'bottom')], + 'bottom-left': [decl('perspective-origin', '0 100%')], + left: [decl('perspective-origin', '0')], + 'top-left': [decl('perspective-origin', '0 0')], + }, }) /** * @css `perspective` */ - staticUtility('perspective-none', [['perspective', 'none']]) functionalUtility('perspective', { themeKeys: ['--perspective'], handle: (value) => [decl('perspective', value)], + staticValues: { + none: [decl('perspective', 'none')], + }, }) let translateProperties = () => @@ -1757,23 +1805,24 @@ export function createUtilities(theme: Theme) { staticUtility('list-inside', [['list-style-position', 'inside']]) staticUtility('list-outside', [['list-style-position', 'outside']]) - /** - * @css `list-style-type` - */ - staticUtility('list-none', [['list-style-type', 'none']]) - staticUtility('list-disc', [['list-style-type', 'disc']]) - staticUtility('list-decimal', [['list-style-type', 'decimal']]) functionalUtility('list', { themeKeys: ['--list-style-type'], handle: (value) => [decl('list-style-type', value)], + staticValues: { + none: [decl('list-style-type', 'none')], + disc: [decl('list-style-type', 'disc')], + decimal: [decl('list-style-type', 'decimal')], + }, }) // list-image-* - staticUtility('list-image-none', [['list-style-image', 'none']]) functionalUtility('list-image', { themeKeys: ['--list-style-image'], handle: (value) => [decl('list-style-image', value)], + staticValues: { + none: [decl('list-style-image', 'none')], + }, }) staticUtility('appearance-none', [['appearance', 'none']]) @@ -1786,9 +1835,6 @@ export function createUtilities(theme: Theme) { staticUtility('scheme-only-dark', [['color-scheme', 'only dark']]) staticUtility('scheme-only-light', [['color-scheme', 'only light']]) - // columns-* - staticUtility('columns-auto', [['columns', 'auto']]) - functionalUtility('columns', { themeKeys: ['--columns', '--container'], handleBareValue: ({ value }) => { @@ -1796,6 +1842,9 @@ export function createUtilities(theme: Theme) { return value }, handle: (value) => [decl('columns', value)], + staticValues: { + auto: [decl('columns', 'auto')], + }, }) suggest('columns', () => [ @@ -1823,26 +1872,28 @@ export function createUtilities(theme: Theme) { staticUtility('grid-flow-row-dense', [['grid-auto-flow', 'row dense']]) staticUtility('grid-flow-col-dense', [['grid-auto-flow', 'column dense']]) - staticUtility('auto-cols-auto', [['grid-auto-columns', 'auto']]) - staticUtility('auto-cols-min', [['grid-auto-columns', 'min-content']]) - staticUtility('auto-cols-max', [['grid-auto-columns', 'max-content']]) - staticUtility('auto-cols-fr', [['grid-auto-columns', 'minmax(0, 1fr)']]) functionalUtility('auto-cols', { themeKeys: ['--grid-auto-columns'], handle: (value) => [decl('grid-auto-columns', value)], + staticValues: { + auto: [decl('grid-auto-columns', 'auto')], + min: [decl('grid-auto-columns', 'min-content')], + max: [decl('grid-auto-columns', 'max-content')], + fr: [decl('grid-auto-columns', 'minmax(0, 1fr)')], + }, }) - staticUtility('auto-rows-auto', [['grid-auto-rows', 'auto']]) - staticUtility('auto-rows-min', [['grid-auto-rows', 'min-content']]) - staticUtility('auto-rows-max', [['grid-auto-rows', 'max-content']]) - staticUtility('auto-rows-fr', [['grid-auto-rows', 'minmax(0, 1fr)']]) functionalUtility('auto-rows', { themeKeys: ['--grid-auto-rows'], handle: (value) => [decl('grid-auto-rows', value)], + staticValues: { + auto: [decl('grid-auto-rows', 'auto')], + min: [decl('grid-auto-rows', 'min-content')], + max: [decl('grid-auto-rows', 'max-content')], + fr: [decl('grid-auto-rows', 'minmax(0, 1fr)')], + }, }) - staticUtility('grid-cols-none', [['grid-template-columns', 'none']]) - staticUtility('grid-cols-subgrid', [['grid-template-columns', 'subgrid']]) functionalUtility('grid-cols', { themeKeys: ['--grid-template-columns'], handleBareValue: ({ value }) => { @@ -1850,10 +1901,12 @@ export function createUtilities(theme: Theme) { return `repeat(${value}, minmax(0, 1fr))` }, handle: (value) => [decl('grid-template-columns', value)], + staticValues: { + none: [decl('grid-template-columns', 'none')], + subgrid: [decl('grid-template-columns', 'subgrid')], + }, }) - staticUtility('grid-rows-none', [['grid-template-rows', 'none']]) - staticUtility('grid-rows-subgrid', [['grid-template-rows', 'subgrid']]) functionalUtility('grid-rows', { themeKeys: ['--grid-template-rows'], handleBareValue: ({ value }) => { @@ -1861,6 +1914,10 @@ export function createUtilities(theme: Theme) { return `repeat(${value}, minmax(0, 1fr))` }, handle: (value) => [decl('grid-template-rows', value)], + staticValues: { + none: [decl('grid-template-rows', 'none')], + subgrid: [decl('grid-template-rows', 'subgrid')], + }, }) suggest('grid-cols', () => [ @@ -2125,17 +2182,13 @@ export function createUtilities(theme: Theme) { ['rounded-br', ['border-bottom-right-radius']], ['rounded-bl', ['border-bottom-left-radius']], ] as const) { - staticUtility( - `${root}-none`, - properties.map((property) => [property, '0']), - ) - staticUtility( - `${root}-full`, - properties.map((property) => [property, 'calc(infinity * 1px)']), - ) functionalUtility(root, { themeKeys: ['--radius'], handle: (value) => properties.map((property) => decl(property, value)), + staticValues: { + none: properties.map((property) => decl(property, '0')), + full: properties.map((property) => decl(property, 'calc(infinity * 1px)')), + }, }) } } @@ -3628,18 +3681,20 @@ export function createUtilities(theme: Theme) { staticUtility('object-none', [['object-fit', 'none']]) staticUtility('object-scale-down', [['object-fit', 'scale-down']]) - staticUtility('object-top', [['object-position', 'top']]) - staticUtility('object-top-left', [['object-position', 'left top']]) - staticUtility('object-top-right', [['object-position', 'right top']]) - staticUtility('object-bottom', [['object-position', 'bottom']]) - staticUtility('object-bottom-left', [['object-position', 'left bottom']]) - staticUtility('object-bottom-right', [['object-position', 'right bottom']]) - staticUtility('object-left', [['object-position', 'left']]) - staticUtility('object-right', [['object-position', 'right']]) - staticUtility('object-center', [['object-position', 'center']]) functionalUtility('object', { themeKeys: ['--object-position'], handle: (value) => [decl('object-position', value)], + staticValues: { + top: [decl('object-position', 'top')], + 'top-left': [decl('object-position', 'left top')], + 'top-right': [decl('object-position', 'right top')], + bottom: [decl('object-position', 'bottom')], + 'bottom-left': [decl('object-position', 'left bottom')], + 'bottom-right': [decl('object-position', 'right bottom')], + left: [decl('object-position', 'left')], + right: [decl('object-position', 'right')], + center: [decl('object-position', 'center')], + }, }) for (let [name, property] of [ @@ -3867,10 +3922,12 @@ export function createUtilities(theme: Theme) { }, ]) - staticUtility('animate-none', [['animation', 'none']]) functionalUtility('animate', { themeKeys: ['--animate'], handle: (value) => [decl('animation', value)], + staticValues: { + none: [decl('animation', 'none')], + }, }) { @@ -3978,10 +4035,11 @@ export function createUtilities(theme: Theme) { decl('--tw-blur', `blur(${value})`), decl('filter', cssFilterValue), ], + staticValues: { + none: [filterProperties(), decl('--tw-blur', ' '), decl('filter', cssFilterValue)], + }, }) - staticUtility('blur-none', [filterProperties, ['--tw-blur', ' '], ['filter', cssFilterValue]]) - functionalUtility('backdrop-blur', { themeKeys: ['--backdrop-blur', '--blur'], handle: (value) => [ @@ -3990,15 +4048,16 @@ export function createUtilities(theme: Theme) { decl('-webkit-backdrop-filter', cssBackdropFilterValue), decl('backdrop-filter', cssBackdropFilterValue), ], + staticValues: { + none: [ + backdropFilterProperties(), + decl('--tw-backdrop-blur', ' '), + decl('-webkit-backdrop-filter', cssBackdropFilterValue), + decl('backdrop-filter', cssBackdropFilterValue), + ], + }, }) - staticUtility('backdrop-blur-none', [ - backdropFilterProperties, - ['--tw-backdrop-blur', ' '], - ['-webkit-backdrop-filter', cssBackdropFilterValue], - ['backdrop-filter', cssBackdropFilterValue], - ]) - functionalUtility('brightness', { themeKeys: ['--brightness'], handleBareValue: ({ value }) => { @@ -4476,36 +4535,6 @@ export function createUtilities(theme: Theme) { let defaultTimingFunction = `var(--tw-ease, ${theme.resolve(null, ['--default-transition-timing-function']) ?? 'ease'})` let defaultDuration = `var(--tw-duration, ${theme.resolve(null, ['--default-transition-duration']) ?? '0s'})` - staticUtility('transition-none', [['transition-property', 'none']]) - staticUtility('transition-all', [ - ['transition-property', 'all'], - ['transition-timing-function', defaultTimingFunction], - ['transition-duration', defaultDuration], - ]) - staticUtility('transition-colors', [ - [ - 'transition-property', - 'color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to', - ], - ['transition-timing-function', defaultTimingFunction], - ['transition-duration', defaultDuration], - ]) - staticUtility('transition-opacity', [ - ['transition-property', 'opacity'], - ['transition-timing-function', defaultTimingFunction], - ['transition-duration', defaultDuration], - ]) - staticUtility('transition-shadow', [ - ['transition-property', 'box-shadow'], - ['transition-timing-function', defaultTimingFunction], - ['transition-duration', defaultDuration], - ]) - staticUtility('transition-transform', [ - ['transition-property', 'transform, translate, scale, rotate'], - ['transition-timing-function', defaultTimingFunction], - ['transition-duration', defaultDuration], - ]) - functionalUtility('transition', { defaultValue: 'color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events', @@ -4515,6 +4544,37 @@ export function createUtilities(theme: Theme) { decl('transition-timing-function', defaultTimingFunction), decl('transition-duration', defaultDuration), ], + staticValues: { + none: [decl('transition-property', 'none')], + all: [ + decl('transition-property', 'all'), + decl('transition-timing-function', defaultTimingFunction), + decl('transition-duration', defaultDuration), + ], + colors: [ + decl( + 'transition-property', + 'color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to', + ), + decl('transition-timing-function', defaultTimingFunction), + decl('transition-duration', defaultDuration), + ], + opacity: [ + decl('transition-property', 'opacity'), + decl('transition-timing-function', defaultTimingFunction), + decl('transition-duration', defaultDuration), + ], + shadow: [ + decl('transition-property', 'box-shadow'), + decl('transition-timing-function', defaultTimingFunction), + decl('transition-duration', defaultDuration), + ], + transform: [ + decl('transition-property', 'transform, translate, scale, rotate'), + decl('transition-timing-function', defaultTimingFunction), + decl('transition-duration', defaultDuration), + ], + }, }) staticUtility('transition-discrete', [['transition-behavior', 'allow-discrete']]) @@ -4590,12 +4650,6 @@ export function createUtilities(theme: Theme) { return atRoot([property('--tw-ease')]) } - staticUtility('ease-initial', [transitionTimingFunctionProperty, ['--tw-ease', 'initial']]) - staticUtility('ease-linear', [ - transitionTimingFunctionProperty, - ['--tw-ease', 'linear'], - ['transition-timing-function', 'linear'], - ]) functionalUtility('ease', { themeKeys: ['--ease'], handle: (value) => [ @@ -4603,6 +4657,14 @@ export function createUtilities(theme: Theme) { decl('--tw-ease', value), decl('transition-timing-function', value), ], + staticValues: { + initial: [transitionTimingFunctionProperty(), decl('--tw-ease', 'initial')], + linear: [ + transitionTimingFunctionProperty(), + decl('--tw-ease', 'linear'), + decl('transition-timing-function', 'linear'), + ], + }, }) } @@ -4683,16 +4745,24 @@ export function createUtilities(theme: Theme) { staticUtility('forced-color-adjust-none', [['forced-color-adjust', 'none']]) staticUtility('forced-color-adjust-auto', [['forced-color-adjust', 'auto']]) - staticUtility('leading-none', [ - () => atRoot([property('--tw-leading')]), - ['--tw-leading', '1'], - ['line-height', '1'], - ]) - spacingUtility('leading', ['--leading', '--spacing'], (value) => [ - atRoot([property('--tw-leading')]), - decl('--tw-leading', value), - decl('line-height', value), - ]) + spacingUtility( + 'leading', + ['--leading', '--spacing'], + (value) => [ + atRoot([property('--tw-leading')]), + decl('--tw-leading', value), + decl('line-height', value), + ], + { + staticValues: { + none: [ + atRoot([property('--tw-leading')]), + decl('--tw-leading', '1'), + decl('line-height', '1'), + ], + }, + }, + ) functionalUtility('tracking', { supportsNegative: true, @@ -4931,7 +5001,6 @@ export function createUtilities(theme: Theme) { }, ]) - staticUtility('underline-offset-auto', [['text-underline-offset', 'auto']]) functionalUtility('underline-offset', { supportsNegative: true, themeKeys: ['--text-underline-offset'], @@ -4940,6 +5009,9 @@ export function createUtilities(theme: Theme) { return `${value}px` }, handle: (value) => [decl('text-underline-offset', value)], + staticValues: { + auto: [decl('text-underline-offset', 'auto')], + }, }) suggest('underline-offset', () => [