diff --git a/CHANGELOG.md b/CHANGELOG.md
index a233dcd54ef1..796a8669a083 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
-- Nothing yet!
+### Fixed
+
+- Only expose used CSS variables ([#16211](https://github.com/tailwindlabs/tailwindcss/pull/16211))
+- Only expose used `@keyframes` ([#16211](https://github.com/tailwindlabs/tailwindcss/pull/16211))
## [4.0.4] - 2025-02-06
diff --git a/crates/oxide/src/parser.rs b/crates/oxide/src/parser.rs
index 12791ae780ac..9cd702d477f3 100644
--- a/crates/oxide/src/parser.rs
+++ b/crates/oxide/src/parser.rs
@@ -340,13 +340,12 @@ impl<'a> Extractor<'a> {
let start_brace_index = utility.find(b"[");
let end_brace_index = utility.find(b"]");
- match (start_brace_index, end_brace_index) {
- (Some(start_brace_index), Some(end_brace_index)) => {
- if start_brace_index < index && end_brace_index > index {
- skip_parens_check = true;
- }
+ if let (Some(start_brace_index), Some(end_brace_index)) =
+ (start_brace_index, end_brace_index)
+ {
+ if start_brace_index < index && end_brace_index > index {
+ skip_parens_check = true;
}
- _ => {}
}
if !skip_parens_check && !utility[index + 1..].starts_with(b"--") {
@@ -1679,6 +1678,54 @@ mod test {
);
}
+ #[test]
+ fn test_find_css_variables() {
+ let candidates = run("var(--color-red-500)", false);
+ assert_eq!(candidates, vec!["var", "--color-red-500"]);
+
+ let candidates = run("
", false);
+ assert_eq!(
+ candidates,
+ vec!["div", "style", "color", "var", "--color-red-500"]
+ );
+ }
+
+ #[test]
+ fn test_find_css_variables_with_fallback_values() {
+ let candidates = run("var(--color-red-500, red)", false);
+ assert_eq!(candidates, vec!["var", "--color-red-500", "red"]);
+
+ let candidates = run("var(--color-red-500,red)", false);
+ assert_eq!(candidates, vec!["var", "--color-red-500", "red"]);
+
+ let candidates = run(
+ "",
+ false,
+ );
+ assert_eq!(
+ candidates,
+ vec!["div", "style", "color", "var", "--color-red-500", "red"]
+ );
+
+ let candidates = run(
+ "",
+ false,
+ );
+ assert_eq!(
+ candidates,
+ vec!["div", "style", "color", "var", "--color-red-500", "red"]
+ );
+ }
+
+ #[test]
+ fn test_find_css_variables_with_fallback_css_variable_values() {
+ let candidates = run("var(--color-red-500, var(--color-blue-500))", false);
+ assert_eq!(
+ candidates,
+ vec!["var", "--color-red-500", "--color-blue-500"]
+ );
+ }
+
#[test]
fn test_is_valid_candidate_string() {
assert_eq!(
diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts
index 0e3aba4c4e88..2eee0413dc4f 100644
--- a/integrations/cli/index.test.ts
+++ b/integrations/cli/index.test.ts
@@ -1253,3 +1253,64 @@ test(
`)
},
)
+
+test(
+ 'emit CSS variables if used outside of utilities',
+ {
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "workspace:^",
+ "@tailwindcss/cli": "workspace:^"
+ }
+ }
+ `,
+ 'src/index.css': css`
+ @import 'tailwindcss/utilities';
+ @theme {
+ --color-blue-500: blue;
+ }
+ `,
+ 'src/index.ts': ts`
+ function MyComponent() {
+ return
+ }
+ `,
+ },
+ },
+ async ({ fs, spawn, expect }) => {
+ let process = await spawn(
+ 'pnpm tailwindcss --input src/index.css --output dist/out.css --watch',
+ )
+ await process.onStderr((m) => m.includes('Done in'))
+
+ // No CSS variables are used, so nothing should be generated yet.
+ expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
+ "
+ --- ./dist/out.css ---
+
+ "
+ `)
+
+ // Use a CSS variable in JS/TS land, now it should be generated.
+ await fs.write(
+ './src/index.ts',
+ ts`
+ function MyComponent() {
+ return
+ }
+ `,
+ )
+
+ fs.expectFileToContain(
+ './dist/out.css',
+ css`
+ :root,
+ :host {
+ --color-blue-500: blue;
+ }
+ `,
+ )
+ },
+)
diff --git a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap
index d401ace41bed..dbf73d755e12 100644
--- a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap
+++ b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap
@@ -4,363 +4,11 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
"@layer theme {
:root, :host {
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
- --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
- --color-red-50: oklch(.971 .013 17.38);
- --color-red-100: oklch(.936 .032 17.717);
- --color-red-200: oklch(.885 .062 18.334);
- --color-red-300: oklch(.808 .114 19.571);
- --color-red-400: oklch(.704 .191 22.216);
- --color-red-500: oklch(.637 .237 25.331);
- --color-red-600: oklch(.577 .245 27.325);
- --color-red-700: oklch(.505 .213 27.518);
- --color-red-800: oklch(.444 .177 26.899);
- --color-red-900: oklch(.396 .141 25.723);
- --color-red-950: oklch(.258 .092 26.042);
- --color-orange-50: oklch(.98 .016 73.684);
- --color-orange-100: oklch(.954 .038 75.164);
- --color-orange-200: oklch(.901 .076 70.697);
- --color-orange-300: oklch(.837 .128 66.29);
- --color-orange-400: oklch(.75 .183 55.934);
- --color-orange-500: oklch(.705 .213 47.604);
- --color-orange-600: oklch(.646 .222 41.116);
- --color-orange-700: oklch(.553 .195 38.402);
- --color-orange-800: oklch(.47 .157 37.304);
- --color-orange-900: oklch(.408 .123 38.172);
- --color-orange-950: oklch(.266 .079 36.259);
- --color-amber-50: oklch(.987 .022 95.277);
- --color-amber-100: oklch(.962 .059 95.617);
- --color-amber-200: oklch(.924 .12 95.746);
- --color-amber-300: oklch(.879 .169 91.605);
- --color-amber-400: oklch(.828 .189 84.429);
- --color-amber-500: oklch(.769 .188 70.08);
- --color-amber-600: oklch(.666 .179 58.318);
- --color-amber-700: oklch(.555 .163 48.998);
- --color-amber-800: oklch(.473 .137 46.201);
- --color-amber-900: oklch(.414 .112 45.904);
- --color-amber-950: oklch(.279 .077 45.635);
- --color-yellow-50: oklch(.987 .026 102.212);
- --color-yellow-100: oklch(.973 .071 103.193);
- --color-yellow-200: oklch(.945 .129 101.54);
- --color-yellow-300: oklch(.905 .182 98.111);
- --color-yellow-400: oklch(.852 .199 91.936);
- --color-yellow-500: oklch(.795 .184 86.047);
- --color-yellow-600: oklch(.681 .162 75.834);
- --color-yellow-700: oklch(.554 .135 66.442);
- --color-yellow-800: oklch(.476 .114 61.907);
- --color-yellow-900: oklch(.421 .095 57.708);
- --color-yellow-950: oklch(.286 .066 53.813);
- --color-lime-50: oklch(.986 .031 120.757);
- --color-lime-100: oklch(.967 .067 122.328);
- --color-lime-200: oklch(.938 .127 124.321);
- --color-lime-300: oklch(.897 .196 126.665);
- --color-lime-400: oklch(.841 .238 128.85);
- --color-lime-500: oklch(.768 .233 130.85);
- --color-lime-600: oklch(.648 .2 131.684);
- --color-lime-700: oklch(.532 .157 131.589);
- --color-lime-800: oklch(.453 .124 130.933);
- --color-lime-900: oklch(.405 .101 131.063);
- --color-lime-950: oklch(.274 .072 132.109);
- --color-green-50: oklch(.982 .018 155.826);
- --color-green-100: oklch(.962 .044 156.743);
- --color-green-200: oklch(.925 .084 155.995);
- --color-green-300: oklch(.871 .15 154.449);
- --color-green-400: oklch(.792 .209 151.711);
- --color-green-500: oklch(.723 .219 149.579);
- --color-green-600: oklch(.627 .194 149.214);
- --color-green-700: oklch(.527 .154 150.069);
- --color-green-800: oklch(.448 .119 151.328);
- --color-green-900: oklch(.393 .095 152.535);
- --color-green-950: oklch(.266 .065 152.934);
- --color-emerald-50: oklch(.979 .021 166.113);
- --color-emerald-100: oklch(.95 .052 163.051);
- --color-emerald-200: oklch(.905 .093 164.15);
- --color-emerald-300: oklch(.845 .143 164.978);
- --color-emerald-400: oklch(.765 .177 163.223);
- --color-emerald-500: oklch(.696 .17 162.48);
- --color-emerald-600: oklch(.596 .145 163.225);
- --color-emerald-700: oklch(.508 .118 165.612);
- --color-emerald-800: oklch(.432 .095 166.913);
- --color-emerald-900: oklch(.378 .077 168.94);
- --color-emerald-950: oklch(.262 .051 172.552);
- --color-teal-50: oklch(.984 .014 180.72);
- --color-teal-100: oklch(.953 .051 180.801);
- --color-teal-200: oklch(.91 .096 180.426);
- --color-teal-300: oklch(.855 .138 181.071);
- --color-teal-400: oklch(.777 .152 181.912);
- --color-teal-500: oklch(.704 .14 182.503);
- --color-teal-600: oklch(.6 .118 184.704);
- --color-teal-700: oklch(.511 .096 186.391);
- --color-teal-800: oklch(.437 .078 188.216);
- --color-teal-900: oklch(.386 .063 188.416);
- --color-teal-950: oklch(.277 .046 192.524);
- --color-cyan-50: oklch(.984 .019 200.873);
- --color-cyan-100: oklch(.956 .045 203.388);
- --color-cyan-200: oklch(.917 .08 205.041);
- --color-cyan-300: oklch(.865 .127 207.078);
- --color-cyan-400: oklch(.789 .154 211.53);
- --color-cyan-500: oklch(.715 .143 215.221);
- --color-cyan-600: oklch(.609 .126 221.723);
- --color-cyan-700: oklch(.52 .105 223.128);
- --color-cyan-800: oklch(.45 .085 224.283);
- --color-cyan-900: oklch(.398 .07 227.392);
- --color-cyan-950: oklch(.302 .056 229.695);
- --color-sky-50: oklch(.977 .013 236.62);
- --color-sky-100: oklch(.951 .026 236.824);
- --color-sky-200: oklch(.901 .058 230.902);
- --color-sky-300: oklch(.828 .111 230.318);
- --color-sky-400: oklch(.746 .16 232.661);
- --color-sky-500: oklch(.685 .169 237.323);
- --color-sky-600: oklch(.588 .158 241.966);
- --color-sky-700: oklch(.5 .134 242.749);
- --color-sky-800: oklch(.443 .11 240.79);
- --color-sky-900: oklch(.391 .09 240.876);
- --color-sky-950: oklch(.293 .066 243.157);
- --color-blue-50: oklch(.97 .014 254.604);
- --color-blue-100: oklch(.932 .032 255.585);
- --color-blue-200: oklch(.882 .059 254.128);
- --color-blue-300: oklch(.809 .105 251.813);
- --color-blue-400: oklch(.707 .165 254.624);
- --color-blue-500: oklch(.623 .214 259.815);
- --color-blue-600: oklch(.546 .245 262.881);
- --color-blue-700: oklch(.488 .243 264.376);
- --color-blue-800: oklch(.424 .199 265.638);
- --color-blue-900: oklch(.379 .146 265.522);
- --color-blue-950: oklch(.282 .091 267.935);
- --color-indigo-50: oklch(.962 .018 272.314);
- --color-indigo-100: oklch(.93 .034 272.788);
- --color-indigo-200: oklch(.87 .065 274.039);
- --color-indigo-300: oklch(.785 .115 274.713);
- --color-indigo-400: oklch(.673 .182 276.935);
- --color-indigo-500: oklch(.585 .233 277.117);
- --color-indigo-600: oklch(.511 .262 276.966);
- --color-indigo-700: oklch(.457 .24 277.023);
- --color-indigo-800: oklch(.398 .195 277.366);
- --color-indigo-900: oklch(.359 .144 278.697);
- --color-indigo-950: oklch(.257 .09 281.288);
- --color-violet-50: oklch(.969 .016 293.756);
- --color-violet-100: oklch(.943 .029 294.588);
- --color-violet-200: oklch(.894 .057 293.283);
- --color-violet-300: oklch(.811 .111 293.571);
- --color-violet-400: oklch(.702 .183 293.541);
- --color-violet-500: oklch(.606 .25 292.717);
- --color-violet-600: oklch(.541 .281 293.009);
- --color-violet-700: oklch(.491 .27 292.581);
- --color-violet-800: oklch(.432 .232 292.759);
- --color-violet-900: oklch(.38 .189 293.745);
- --color-violet-950: oklch(.283 .141 291.089);
- --color-purple-50: oklch(.977 .014 308.299);
- --color-purple-100: oklch(.946 .033 307.174);
- --color-purple-200: oklch(.902 .063 306.703);
- --color-purple-300: oklch(.827 .119 306.383);
- --color-purple-400: oklch(.714 .203 305.504);
- --color-purple-500: oklch(.627 .265 303.9);
- --color-purple-600: oklch(.558 .288 302.321);
- --color-purple-700: oklch(.496 .265 301.924);
- --color-purple-800: oklch(.438 .218 303.724);
- --color-purple-900: oklch(.381 .176 304.987);
- --color-purple-950: oklch(.291 .149 302.717);
- --color-fuchsia-50: oklch(.977 .017 320.058);
- --color-fuchsia-100: oklch(.952 .037 318.852);
- --color-fuchsia-200: oklch(.903 .076 319.62);
- --color-fuchsia-300: oklch(.833 .145 321.434);
- --color-fuchsia-400: oklch(.74 .238 322.16);
- --color-fuchsia-500: oklch(.667 .295 322.15);
- --color-fuchsia-600: oklch(.591 .293 322.896);
- --color-fuchsia-700: oklch(.518 .253 323.949);
- --color-fuchsia-800: oklch(.452 .211 324.591);
- --color-fuchsia-900: oklch(.401 .17 325.612);
- --color-fuchsia-950: oklch(.293 .136 325.661);
- --color-pink-50: oklch(.971 .014 343.198);
- --color-pink-100: oklch(.948 .028 342.258);
- --color-pink-200: oklch(.899 .061 343.231);
- --color-pink-300: oklch(.823 .12 346.018);
- --color-pink-400: oklch(.718 .202 349.761);
- --color-pink-500: oklch(.656 .241 354.308);
- --color-pink-600: oklch(.592 .249 .584);
- --color-pink-700: oklch(.525 .223 3.958);
- --color-pink-800: oklch(.459 .187 3.815);
- --color-pink-900: oklch(.408 .153 2.432);
- --color-pink-950: oklch(.284 .109 3.907);
- --color-rose-50: oklch(.969 .015 12.422);
- --color-rose-100: oklch(.941 .03 12.58);
- --color-rose-200: oklch(.892 .058 10.001);
- --color-rose-300: oklch(.81 .117 11.638);
- --color-rose-400: oklch(.712 .194 13.428);
- --color-rose-500: oklch(.645 .246 16.439);
- --color-rose-600: oklch(.586 .253 17.585);
- --color-rose-700: oklch(.514 .222 16.935);
- --color-rose-800: oklch(.455 .188 13.697);
- --color-rose-900: oklch(.41 .159 10.272);
- --color-rose-950: oklch(.271 .105 12.094);
- --color-slate-50: oklch(.984 .003 247.858);
- --color-slate-100: oklch(.968 .007 247.896);
- --color-slate-200: oklch(.929 .013 255.508);
- --color-slate-300: oklch(.869 .022 252.894);
- --color-slate-400: oklch(.704 .04 256.788);
- --color-slate-500: oklch(.554 .046 257.417);
- --color-slate-600: oklch(.446 .043 257.281);
- --color-slate-700: oklch(.372 .044 257.287);
- --color-slate-800: oklch(.279 .041 260.031);
- --color-slate-900: oklch(.208 .042 265.755);
- --color-slate-950: oklch(.129 .042 264.695);
- --color-gray-50: oklch(.985 .002 247.839);
- --color-gray-100: oklch(.967 .003 264.542);
- --color-gray-200: oklch(.928 .006 264.531);
- --color-gray-300: oklch(.872 .01 258.338);
- --color-gray-400: oklch(.707 .022 261.325);
- --color-gray-500: oklch(.551 .027 264.364);
- --color-gray-600: oklch(.446 .03 256.802);
- --color-gray-700: oklch(.373 .034 259.733);
- --color-gray-800: oklch(.278 .033 256.848);
- --color-gray-900: oklch(.21 .034 264.665);
- --color-gray-950: oklch(.13 .028 261.692);
- --color-zinc-50: oklch(.985 0 0);
- --color-zinc-100: oklch(.967 .001 286.375);
- --color-zinc-200: oklch(.92 .004 286.32);
- --color-zinc-300: oklch(.871 .006 286.286);
- --color-zinc-400: oklch(.705 .015 286.067);
- --color-zinc-500: oklch(.552 .016 285.938);
- --color-zinc-600: oklch(.442 .017 285.786);
- --color-zinc-700: oklch(.37 .013 285.805);
- --color-zinc-800: oklch(.274 .006 286.033);
- --color-zinc-900: oklch(.21 .006 285.885);
- --color-zinc-950: oklch(.141 .005 285.823);
- --color-neutral-50: oklch(.985 0 0);
- --color-neutral-100: oklch(.97 0 0);
- --color-neutral-200: oklch(.922 0 0);
- --color-neutral-300: oklch(.87 0 0);
- --color-neutral-400: oklch(.708 0 0);
- --color-neutral-500: oklch(.556 0 0);
- --color-neutral-600: oklch(.439 0 0);
- --color-neutral-700: oklch(.371 0 0);
- --color-neutral-800: oklch(.269 0 0);
- --color-neutral-900: oklch(.205 0 0);
- --color-neutral-950: oklch(.145 0 0);
- --color-stone-50: oklch(.985 .001 106.423);
- --color-stone-100: oklch(.97 .001 106.424);
- --color-stone-200: oklch(.923 .003 48.717);
- --color-stone-300: oklch(.869 .005 56.366);
- --color-stone-400: oklch(.709 .01 56.259);
- --color-stone-500: oklch(.553 .013 58.071);
- --color-stone-600: oklch(.444 .011 73.639);
- --color-stone-700: oklch(.374 .01 67.558);
- --color-stone-800: oklch(.268 .007 34.298);
- --color-stone-900: oklch(.216 .006 56.043);
- --color-stone-950: oklch(.147 .004 49.25);
--color-black: #000;
- --color-white: #fff;
- --spacing: .25rem;
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- --container-3xs: 16rem;
- --container-2xs: 18rem;
- --container-xs: 20rem;
- --container-sm: 24rem;
- --container-md: 28rem;
- --container-lg: 32rem;
- --container-xl: 36rem;
- --container-2xl: 42rem;
- --container-3xl: 48rem;
- --container-4xl: 56rem;
- --container-5xl: 64rem;
- --container-6xl: 72rem;
- --container-7xl: 80rem;
- --text-xs: .75rem;
- --text-xs--line-height: calc(1 / .75);
- --text-sm: .875rem;
- --text-sm--line-height: calc(1.25 / .875);
- --text-base: 1rem;
- --text-base--line-height: calc(1.5 / 1);
- --text-lg: 1.125rem;
- --text-lg--line-height: calc(1.75 / 1.125);
- --text-xl: 1.25rem;
- --text-xl--line-height: calc(1.75 / 1.25);
--text-2xl: 1.5rem;
--text-2xl--line-height: calc(2 / 1.5);
- --text-3xl: 1.875rem;
- --text-3xl--line-height: calc(2.25 / 1.875);
- --text-4xl: 2.25rem;
- --text-4xl--line-height: calc(2.5 / 2.25);
- --text-5xl: 3rem;
- --text-5xl--line-height: 1;
- --text-6xl: 3.75rem;
- --text-6xl--line-height: 1;
- --text-7xl: 4.5rem;
- --text-7xl--line-height: 1;
- --text-8xl: 6rem;
- --text-8xl--line-height: 1;
- --text-9xl: 8rem;
- --text-9xl--line-height: 1;
- --font-weight-thin: 100;
- --font-weight-extralight: 200;
- --font-weight-light: 300;
- --font-weight-normal: 400;
- --font-weight-medium: 500;
- --font-weight-semibold: 600;
--font-weight-bold: 700;
- --font-weight-extrabold: 800;
- --font-weight-black: 900;
- --tracking-tighter: -.05em;
- --tracking-tight: -.025em;
- --tracking-normal: 0em;
- --tracking-wide: .025em;
- --tracking-wider: .05em;
- --tracking-widest: .1em;
- --leading-tight: 1.25;
- --leading-snug: 1.375;
- --leading-normal: 1.5;
- --leading-relaxed: 1.625;
- --leading-loose: 2;
- --radius-xs: .125rem;
- --radius-sm: .25rem;
- --radius-md: .375rem;
- --radius-lg: .5rem;
- --radius-xl: .75rem;
- --radius-2xl: 1rem;
- --radius-3xl: 1.5rem;
- --radius-4xl: 2rem;
- --shadow-2xs: 0 1px #0000000d;
- --shadow-xs: 0 1px 2px 0 #0000000d;
- --shadow-sm: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --shadow-md: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --shadow-lg: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
- --shadow-xl: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
- --shadow-2xl: 0 25px 50px -12px #00000040;
- --inset-shadow-2xs: inset 0 1px #0000000d;
- --inset-shadow-xs: inset 0 1px 1px #0000000d;
- --inset-shadow-sm: inset 0 2px 4px #0000000d;
- --drop-shadow-xs: 0 1px 1px #0000000d;
- --drop-shadow-sm: 0 1px 2px #00000026;
- --drop-shadow-md: 0 3px 3px #0000001f;
- --drop-shadow-lg: 0 4px 4px #00000026;
- --drop-shadow-xl: 0 9px 7px #0000001a;
- --drop-shadow-2xl: 0 25px 25px #00000026;
- --ease-in: cubic-bezier(.4, 0, 1, 1);
- --ease-out: cubic-bezier(0, 0, .2, 1);
- --ease-in-out: cubic-bezier(.4, 0, .2, 1);
- --animate-spin: spin 1s linear infinite;
- --animate-ping: ping 1s cubic-bezier(0, 0, .2, 1) infinite;
- --animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;
- --animate-bounce: bounce 1s infinite;
- --blur-xs: 4px;
- --blur-sm: 8px;
- --blur-md: 12px;
- --blur-lg: 16px;
- --blur-xl: 24px;
- --blur-2xl: 40px;
- --blur-3xl: 64px;
- --perspective-dramatic: 100px;
- --perspective-near: 300px;
- --perspective-normal: 500px;
- --perspective-midrange: 800px;
- --perspective-distant: 1200px;
- --aspect-video: 16 / 9;
- --default-transition-duration: .15s;
- --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
--default-font-family: var(--font-sans);
--default-font-feature-settings: var(--font-sans--font-feature-settings);
--default-font-variation-settings: var(--font-sans--font-variation-settings);
@@ -630,37 +278,6 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
}
}
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
-}
-
-@keyframes ping {
- 75%, 100% {
- opacity: 0;
- transform: scale(2);
- }
-}
-
-@keyframes pulse {
- 50% {
- opacity: .5;
- }
-}
-
-@keyframes bounce {
- 0%, 100% {
- animation-timing-function: cubic-bezier(.8, 0, 1, 1);
- transform: translateY(-25%);
- }
-
- 50% {
- animation-timing-function: cubic-bezier(0, 0, .2, 1);
- transform: none;
- }
-}
-
@property --tw-font-weight {
syntax: "*";
inherits: false
diff --git a/packages/@tailwindcss-postcss/src/index.test.ts b/packages/@tailwindcss-postcss/src/index.test.ts
index 97eee3c1c5f0..3a7e1b8b896c 100644
--- a/packages/@tailwindcss-postcss/src/index.test.ts
+++ b/packages/@tailwindcss-postcss/src/index.test.ts
@@ -330,11 +330,7 @@ test('runs `Once` plugins in the right order', async () => {
)
expect(result.css.trim()).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .custom-css {
+ ".custom-css {
color: red;
}"
`)
@@ -347,11 +343,7 @@ test('runs `Once` plugins in the right order', async () => {
}"
`)
expect(after).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .custom-css {
+ ".custom-css {
color: red;
}"
`)
diff --git a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap
index 9ed291c3908a..0b32f5ed7588 100644
--- a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap
+++ b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap
@@ -3,369 +3,9 @@
exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using the default theme 1`] = `
":root, :host {
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
- --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
- --color-red-50: oklch(.971 .013 17.38);
- --color-red-100: oklch(.936 .032 17.717);
- --color-red-200: oklch(.885 .062 18.334);
- --color-red-300: oklch(.808 .114 19.571);
- --color-red-400: oklch(.704 .191 22.216);
--color-red-500: oklch(.637 .237 25.331);
- --color-red-600: oklch(.577 .245 27.325);
- --color-red-700: oklch(.505 .213 27.518);
- --color-red-800: oklch(.444 .177 26.899);
- --color-red-900: oklch(.396 .141 25.723);
- --color-red-950: oklch(.258 .092 26.042);
- --color-orange-50: oklch(.98 .016 73.684);
- --color-orange-100: oklch(.954 .038 75.164);
- --color-orange-200: oklch(.901 .076 70.697);
- --color-orange-300: oklch(.837 .128 66.29);
- --color-orange-400: oklch(.75 .183 55.934);
- --color-orange-500: oklch(.705 .213 47.604);
- --color-orange-600: oklch(.646 .222 41.116);
- --color-orange-700: oklch(.553 .195 38.402);
- --color-orange-800: oklch(.47 .157 37.304);
- --color-orange-900: oklch(.408 .123 38.172);
- --color-orange-950: oklch(.266 .079 36.259);
- --color-amber-50: oklch(.987 .022 95.277);
- --color-amber-100: oklch(.962 .059 95.617);
- --color-amber-200: oklch(.924 .12 95.746);
- --color-amber-300: oklch(.879 .169 91.605);
- --color-amber-400: oklch(.828 .189 84.429);
- --color-amber-500: oklch(.769 .188 70.08);
- --color-amber-600: oklch(.666 .179 58.318);
- --color-amber-700: oklch(.555 .163 48.998);
- --color-amber-800: oklch(.473 .137 46.201);
- --color-amber-900: oklch(.414 .112 45.904);
- --color-amber-950: oklch(.279 .077 45.635);
- --color-yellow-50: oklch(.987 .026 102.212);
- --color-yellow-100: oklch(.973 .071 103.193);
- --color-yellow-200: oklch(.945 .129 101.54);
- --color-yellow-300: oklch(.905 .182 98.111);
- --color-yellow-400: oklch(.852 .199 91.936);
- --color-yellow-500: oklch(.795 .184 86.047);
- --color-yellow-600: oklch(.681 .162 75.834);
- --color-yellow-700: oklch(.554 .135 66.442);
- --color-yellow-800: oklch(.476 .114 61.907);
- --color-yellow-900: oklch(.421 .095 57.708);
- --color-yellow-950: oklch(.286 .066 53.813);
- --color-lime-50: oklch(.986 .031 120.757);
- --color-lime-100: oklch(.967 .067 122.328);
- --color-lime-200: oklch(.938 .127 124.321);
- --color-lime-300: oklch(.897 .196 126.665);
- --color-lime-400: oklch(.841 .238 128.85);
- --color-lime-500: oklch(.768 .233 130.85);
- --color-lime-600: oklch(.648 .2 131.684);
- --color-lime-700: oklch(.532 .157 131.589);
- --color-lime-800: oklch(.453 .124 130.933);
- --color-lime-900: oklch(.405 .101 131.063);
- --color-lime-950: oklch(.274 .072 132.109);
- --color-green-50: oklch(.982 .018 155.826);
- --color-green-100: oklch(.962 .044 156.743);
- --color-green-200: oklch(.925 .084 155.995);
- --color-green-300: oklch(.871 .15 154.449);
- --color-green-400: oklch(.792 .209 151.711);
- --color-green-500: oklch(.723 .219 149.579);
- --color-green-600: oklch(.627 .194 149.214);
- --color-green-700: oklch(.527 .154 150.069);
- --color-green-800: oklch(.448 .119 151.328);
- --color-green-900: oklch(.393 .095 152.535);
- --color-green-950: oklch(.266 .065 152.934);
- --color-emerald-50: oklch(.979 .021 166.113);
- --color-emerald-100: oklch(.95 .052 163.051);
- --color-emerald-200: oklch(.905 .093 164.15);
- --color-emerald-300: oklch(.845 .143 164.978);
- --color-emerald-400: oklch(.765 .177 163.223);
- --color-emerald-500: oklch(.696 .17 162.48);
- --color-emerald-600: oklch(.596 .145 163.225);
- --color-emerald-700: oklch(.508 .118 165.612);
- --color-emerald-800: oklch(.432 .095 166.913);
- --color-emerald-900: oklch(.378 .077 168.94);
- --color-emerald-950: oklch(.262 .051 172.552);
- --color-teal-50: oklch(.984 .014 180.72);
- --color-teal-100: oklch(.953 .051 180.801);
- --color-teal-200: oklch(.91 .096 180.426);
- --color-teal-300: oklch(.855 .138 181.071);
- --color-teal-400: oklch(.777 .152 181.912);
- --color-teal-500: oklch(.704 .14 182.503);
- --color-teal-600: oklch(.6 .118 184.704);
- --color-teal-700: oklch(.511 .096 186.391);
- --color-teal-800: oklch(.437 .078 188.216);
- --color-teal-900: oklch(.386 .063 188.416);
- --color-teal-950: oklch(.277 .046 192.524);
- --color-cyan-50: oklch(.984 .019 200.873);
- --color-cyan-100: oklch(.956 .045 203.388);
- --color-cyan-200: oklch(.917 .08 205.041);
- --color-cyan-300: oklch(.865 .127 207.078);
- --color-cyan-400: oklch(.789 .154 211.53);
- --color-cyan-500: oklch(.715 .143 215.221);
- --color-cyan-600: oklch(.609 .126 221.723);
- --color-cyan-700: oklch(.52 .105 223.128);
- --color-cyan-800: oklch(.45 .085 224.283);
- --color-cyan-900: oklch(.398 .07 227.392);
- --color-cyan-950: oklch(.302 .056 229.695);
- --color-sky-50: oklch(.977 .013 236.62);
- --color-sky-100: oklch(.951 .026 236.824);
- --color-sky-200: oklch(.901 .058 230.902);
- --color-sky-300: oklch(.828 .111 230.318);
- --color-sky-400: oklch(.746 .16 232.661);
- --color-sky-500: oklch(.685 .169 237.323);
- --color-sky-600: oklch(.588 .158 241.966);
- --color-sky-700: oklch(.5 .134 242.749);
- --color-sky-800: oklch(.443 .11 240.79);
- --color-sky-900: oklch(.391 .09 240.876);
- --color-sky-950: oklch(.293 .066 243.157);
- --color-blue-50: oklch(.97 .014 254.604);
- --color-blue-100: oklch(.932 .032 255.585);
- --color-blue-200: oklch(.882 .059 254.128);
- --color-blue-300: oklch(.809 .105 251.813);
- --color-blue-400: oklch(.707 .165 254.624);
- --color-blue-500: oklch(.623 .214 259.815);
- --color-blue-600: oklch(.546 .245 262.881);
- --color-blue-700: oklch(.488 .243 264.376);
- --color-blue-800: oklch(.424 .199 265.638);
- --color-blue-900: oklch(.379 .146 265.522);
- --color-blue-950: oklch(.282 .091 267.935);
- --color-indigo-50: oklch(.962 .018 272.314);
- --color-indigo-100: oklch(.93 .034 272.788);
- --color-indigo-200: oklch(.87 .065 274.039);
- --color-indigo-300: oklch(.785 .115 274.713);
- --color-indigo-400: oklch(.673 .182 276.935);
- --color-indigo-500: oklch(.585 .233 277.117);
- --color-indigo-600: oklch(.511 .262 276.966);
- --color-indigo-700: oklch(.457 .24 277.023);
- --color-indigo-800: oklch(.398 .195 277.366);
- --color-indigo-900: oklch(.359 .144 278.697);
- --color-indigo-950: oklch(.257 .09 281.288);
- --color-violet-50: oklch(.969 .016 293.756);
- --color-violet-100: oklch(.943 .029 294.588);
- --color-violet-200: oklch(.894 .057 293.283);
- --color-violet-300: oklch(.811 .111 293.571);
- --color-violet-400: oklch(.702 .183 293.541);
- --color-violet-500: oklch(.606 .25 292.717);
- --color-violet-600: oklch(.541 .281 293.009);
- --color-violet-700: oklch(.491 .27 292.581);
- --color-violet-800: oklch(.432 .232 292.759);
- --color-violet-900: oklch(.38 .189 293.745);
- --color-violet-950: oklch(.283 .141 291.089);
- --color-purple-50: oklch(.977 .014 308.299);
- --color-purple-100: oklch(.946 .033 307.174);
- --color-purple-200: oklch(.902 .063 306.703);
- --color-purple-300: oklch(.827 .119 306.383);
- --color-purple-400: oklch(.714 .203 305.504);
- --color-purple-500: oklch(.627 .265 303.9);
- --color-purple-600: oklch(.558 .288 302.321);
- --color-purple-700: oklch(.496 .265 301.924);
- --color-purple-800: oklch(.438 .218 303.724);
- --color-purple-900: oklch(.381 .176 304.987);
- --color-purple-950: oklch(.291 .149 302.717);
- --color-fuchsia-50: oklch(.977 .017 320.058);
- --color-fuchsia-100: oklch(.952 .037 318.852);
- --color-fuchsia-200: oklch(.903 .076 319.62);
- --color-fuchsia-300: oklch(.833 .145 321.434);
- --color-fuchsia-400: oklch(.74 .238 322.16);
- --color-fuchsia-500: oklch(.667 .295 322.15);
- --color-fuchsia-600: oklch(.591 .293 322.896);
- --color-fuchsia-700: oklch(.518 .253 323.949);
- --color-fuchsia-800: oklch(.452 .211 324.591);
- --color-fuchsia-900: oklch(.401 .17 325.612);
- --color-fuchsia-950: oklch(.293 .136 325.661);
- --color-pink-50: oklch(.971 .014 343.198);
- --color-pink-100: oklch(.948 .028 342.258);
- --color-pink-200: oklch(.899 .061 343.231);
- --color-pink-300: oklch(.823 .12 346.018);
- --color-pink-400: oklch(.718 .202 349.761);
- --color-pink-500: oklch(.656 .241 354.308);
- --color-pink-600: oklch(.592 .249 .584);
- --color-pink-700: oklch(.525 .223 3.958);
- --color-pink-800: oklch(.459 .187 3.815);
- --color-pink-900: oklch(.408 .153 2.432);
- --color-pink-950: oklch(.284 .109 3.907);
- --color-rose-50: oklch(.969 .015 12.422);
- --color-rose-100: oklch(.941 .03 12.58);
- --color-rose-200: oklch(.892 .058 10.001);
- --color-rose-300: oklch(.81 .117 11.638);
- --color-rose-400: oklch(.712 .194 13.428);
- --color-rose-500: oklch(.645 .246 16.439);
- --color-rose-600: oklch(.586 .253 17.585);
- --color-rose-700: oklch(.514 .222 16.935);
- --color-rose-800: oklch(.455 .188 13.697);
- --color-rose-900: oklch(.41 .159 10.272);
- --color-rose-950: oklch(.271 .105 12.094);
- --color-slate-50: oklch(.984 .003 247.858);
- --color-slate-100: oklch(.968 .007 247.896);
- --color-slate-200: oklch(.929 .013 255.508);
- --color-slate-300: oklch(.869 .022 252.894);
- --color-slate-400: oklch(.704 .04 256.788);
- --color-slate-500: oklch(.554 .046 257.417);
- --color-slate-600: oklch(.446 .043 257.281);
- --color-slate-700: oklch(.372 .044 257.287);
- --color-slate-800: oklch(.279 .041 260.031);
- --color-slate-900: oklch(.208 .042 265.755);
- --color-slate-950: oklch(.129 .042 264.695);
- --color-gray-50: oklch(.985 .002 247.839);
- --color-gray-100: oklch(.967 .003 264.542);
- --color-gray-200: oklch(.928 .006 264.531);
- --color-gray-300: oklch(.872 .01 258.338);
- --color-gray-400: oklch(.707 .022 261.325);
- --color-gray-500: oklch(.551 .027 264.364);
- --color-gray-600: oklch(.446 .03 256.802);
- --color-gray-700: oklch(.373 .034 259.733);
- --color-gray-800: oklch(.278 .033 256.848);
- --color-gray-900: oklch(.21 .034 264.665);
- --color-gray-950: oklch(.13 .028 261.692);
- --color-zinc-50: oklch(.985 0 0);
- --color-zinc-100: oklch(.967 .001 286.375);
- --color-zinc-200: oklch(.92 .004 286.32);
- --color-zinc-300: oklch(.871 .006 286.286);
- --color-zinc-400: oklch(.705 .015 286.067);
- --color-zinc-500: oklch(.552 .016 285.938);
- --color-zinc-600: oklch(.442 .017 285.786);
- --color-zinc-700: oklch(.37 .013 285.805);
- --color-zinc-800: oklch(.274 .006 286.033);
- --color-zinc-900: oklch(.21 .006 285.885);
- --color-zinc-950: oklch(.141 .005 285.823);
- --color-neutral-50: oklch(.985 0 0);
- --color-neutral-100: oklch(.97 0 0);
- --color-neutral-200: oklch(.922 0 0);
- --color-neutral-300: oklch(.87 0 0);
- --color-neutral-400: oklch(.708 0 0);
- --color-neutral-500: oklch(.556 0 0);
- --color-neutral-600: oklch(.439 0 0);
- --color-neutral-700: oklch(.371 0 0);
- --color-neutral-800: oklch(.269 0 0);
- --color-neutral-900: oklch(.205 0 0);
- --color-neutral-950: oklch(.145 0 0);
- --color-stone-50: oklch(.985 .001 106.423);
- --color-stone-100: oklch(.97 .001 106.424);
- --color-stone-200: oklch(.923 .003 48.717);
- --color-stone-300: oklch(.869 .005 56.366);
- --color-stone-400: oklch(.709 .01 56.259);
- --color-stone-500: oklch(.553 .013 58.071);
- --color-stone-600: oklch(.444 .011 73.639);
- --color-stone-700: oklch(.374 .01 67.558);
- --color-stone-800: oklch(.268 .007 34.298);
- --color-stone-900: oklch(.216 .006 56.043);
- --color-stone-950: oklch(.147 .004 49.25);
- --color-black: #000;
- --color-white: #fff;
--spacing: .25rem;
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- --container-3xs: 16rem;
- --container-2xs: 18rem;
- --container-xs: 20rem;
- --container-sm: 24rem;
- --container-md: 28rem;
- --container-lg: 32rem;
- --container-xl: 36rem;
- --container-2xl: 42rem;
- --container-3xl: 48rem;
- --container-4xl: 56rem;
- --container-5xl: 64rem;
- --container-6xl: 72rem;
- --container-7xl: 80rem;
- --text-xs: .75rem;
- --text-xs--line-height: calc(1 / .75);
- --text-sm: .875rem;
- --text-sm--line-height: calc(1.25 / .875);
- --text-base: 1rem;
- --text-base--line-height: calc(1.5 / 1);
- --text-lg: 1.125rem;
- --text-lg--line-height: calc(1.75 / 1.125);
- --text-xl: 1.25rem;
- --text-xl--line-height: calc(1.75 / 1.25);
- --text-2xl: 1.5rem;
- --text-2xl--line-height: calc(2 / 1.5);
- --text-3xl: 1.875rem;
- --text-3xl--line-height: calc(2.25 / 1.875);
- --text-4xl: 2.25rem;
- --text-4xl--line-height: calc(2.5 / 2.25);
- --text-5xl: 3rem;
- --text-5xl--line-height: 1;
- --text-6xl: 3.75rem;
- --text-6xl--line-height: 1;
- --text-7xl: 4.5rem;
- --text-7xl--line-height: 1;
- --text-8xl: 6rem;
- --text-8xl--line-height: 1;
- --text-9xl: 8rem;
- --text-9xl--line-height: 1;
- --font-weight-thin: 100;
- --font-weight-extralight: 200;
- --font-weight-light: 300;
- --font-weight-normal: 400;
- --font-weight-medium: 500;
- --font-weight-semibold: 600;
- --font-weight-bold: 700;
- --font-weight-extrabold: 800;
- --font-weight-black: 900;
- --tracking-tighter: -.05em;
- --tracking-tight: -.025em;
- --tracking-normal: 0em;
- --tracking-wide: .025em;
- --tracking-wider: .05em;
- --tracking-widest: .1em;
- --leading-tight: 1.25;
- --leading-snug: 1.375;
- --leading-normal: 1.5;
- --leading-relaxed: 1.625;
- --leading-loose: 2;
- --radius-xs: .125rem;
- --radius-sm: .25rem;
- --radius-md: .375rem;
- --radius-lg: .5rem;
- --radius-xl: .75rem;
- --radius-2xl: 1rem;
- --radius-3xl: 1.5rem;
- --radius-4xl: 2rem;
- --shadow-2xs: 0 1px #0000000d;
- --shadow-xs: 0 1px 2px 0 #0000000d;
- --shadow-sm: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --shadow-md: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --shadow-lg: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
- --shadow-xl: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
- --shadow-2xl: 0 25px 50px -12px #00000040;
- --inset-shadow-2xs: inset 0 1px #0000000d;
- --inset-shadow-xs: inset 0 1px 1px #0000000d;
- --inset-shadow-sm: inset 0 2px 4px #0000000d;
- --drop-shadow-xs: 0 1px 1px #0000000d;
- --drop-shadow-sm: 0 1px 2px #00000026;
- --drop-shadow-md: 0 3px 3px #0000001f;
- --drop-shadow-lg: 0 4px 4px #00000026;
- --drop-shadow-xl: 0 9px 7px #0000001a;
- --drop-shadow-2xl: 0 25px 25px #00000026;
- --ease-in: cubic-bezier(.4, 0, 1, 1);
- --ease-out: cubic-bezier(0, 0, .2, 1);
- --ease-in-out: cubic-bezier(.4, 0, .2, 1);
- --animate-spin: spin 1s linear infinite;
- --animate-ping: ping 1s cubic-bezier(0, 0, .2, 1) infinite;
- --animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;
- --animate-bounce: bounce 1s infinite;
- --blur-xs: 4px;
- --blur-sm: 8px;
- --blur-md: 12px;
- --blur-lg: 16px;
- --blur-xl: 24px;
- --blur-2xl: 40px;
- --blur-3xl: 64px;
- --perspective-dramatic: 100px;
- --perspective-near: 300px;
- --perspective-normal: 500px;
- --perspective-midrange: 800px;
- --perspective-distant: 1200px;
- --aspect-video: 16 / 9;
- --default-transition-duration: .15s;
- --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
- --default-font-family: var(--font-sans);
- --default-font-feature-settings: var(--font-sans--font-feature-settings);
- --default-font-variation-settings: var(--font-sans--font-variation-settings);
- --default-mono-font-family: var(--font-mono);
- --default-mono-font-feature-settings: var(--font-mono--font-feature-settings);
- --default-mono-font-variation-settings: var(--font-mono--font-variation-settings);
}
.w-4 {
@@ -387,37 +27,6 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using
}
}
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
-}
-
-@keyframes ping {
- 75%, 100% {
- opacity: 0;
- transform: scale(2);
- }
-}
-
-@keyframes pulse {
- 50% {
- opacity: .5;
- }
-}
-
-@keyframes bounce {
- 0%, 100% {
- animation-timing-function: cubic-bezier(.8, 0, 1, 1);
- transform: translateY(-25%);
- }
-
- 50% {
- animation-timing-function: cubic-bezier(0, 0, .2, 1);
- transform: none;
- }
-}
-
@property --tw-shadow {
syntax: "*";
inherits: false;
diff --git a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap
index 0fe26f6582b5..9d35f62cc069 100644
--- a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap
+++ b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap
@@ -2,9 +2,6 @@
exports[`border-* 1`] = `
":root, :host {
- --radius-none: 0px;
- --radius-full: 9999px;
- --radius-sm: .125rem;
--color-red-500: #ef4444;
}
@@ -127,9 +124,6 @@ exports[`border-* 1`] = `
exports[`border-b-* 1`] = `
":root, :host {
- --radius-none: 0px;
- --radius-full: 9999px;
- --radius-sm: .125rem;
--color-red-500: #ef4444;
}
@@ -252,9 +246,6 @@ exports[`border-b-* 1`] = `
exports[`border-e-* 1`] = `
":root, :host {
- --radius-none: 0px;
- --radius-full: 9999px;
- --radius-sm: .125rem;
--color-red-500: #ef4444;
}
@@ -377,9 +368,6 @@ exports[`border-e-* 1`] = `
exports[`border-l-* 1`] = `
":root, :host {
- --radius-none: 0px;
- --radius-full: 9999px;
- --radius-sm: .125rem;
--color-red-500: #ef4444;
}
@@ -502,9 +490,6 @@ exports[`border-l-* 1`] = `
exports[`border-r-* 1`] = `
":root, :host {
- --radius-none: 0px;
- --radius-full: 9999px;
- --radius-sm: .125rem;
--color-red-500: #ef4444;
}
@@ -627,9 +612,6 @@ exports[`border-r-* 1`] = `
exports[`border-s-* 1`] = `
":root, :host {
- --radius-none: 0px;
- --radius-full: 9999px;
- --radius-sm: .125rem;
--color-red-500: #ef4444;
}
@@ -752,9 +734,6 @@ exports[`border-s-* 1`] = `
exports[`border-t-* 1`] = `
":root, :host {
- --radius-none: 0px;
- --radius-full: 9999px;
- --radius-sm: .125rem;
--color-red-500: #ef4444;
}
@@ -877,9 +856,6 @@ exports[`border-t-* 1`] = `
exports[`border-x-* 1`] = `
":root, :host {
- --radius-none: 0px;
- --radius-full: 9999px;
- --radius-sm: .125rem;
--color-red-500: #ef4444;
}
@@ -1002,9 +978,6 @@ exports[`border-x-* 1`] = `
exports[`border-y-* 1`] = `
":root, :host {
- --radius-none: 0px;
- --radius-full: 9999px;
- --radius-sm: .125rem;
--color-red-500: #ef4444;
}
diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts
index 0e9215bbec0f..559accd020cc 100644
--- a/packages/tailwindcss/src/ast.ts
+++ b/packages/tailwindcss/src/ast.ts
@@ -1,4 +1,7 @@
import { parseAtRule } from './css-parser'
+import type { DesignSystem } from './design-system'
+import { ThemeOptions } from './theme'
+import { DefaultMap } from './utils/default-map'
const AT_SIGN = 0x40
@@ -252,13 +255,20 @@ export function walkDepth(
// Optimize the AST for printing where all the special nodes that require custom
// handling are handled such that the printing is a 1-to-1 transformation.
-export function optimizeAst(ast: AstNode[]) {
+export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) {
let atRoots: AstNode[] = []
let seenAtProperties = new Set()
+ let cssThemeVariables = new DefaultMap<
+ Extract['nodes'],
+ Set
+ >(() => new Set())
+ let keyframes = new Set()
+ let usedKeyframeNames = new Set()
function transform(
node: AstNode,
parent: Extract['nodes'],
+ context: Record = {},
depth = 0,
) {
// Declaration
@@ -266,6 +276,23 @@ export function optimizeAst(ast: AstNode[]) {
if (node.property === '--tw-sort' || node.value === undefined || node.value === null) {
return
}
+
+ // Track variables defined in `@theme`
+ if (context.theme && node.property[0] === '-' && node.property[1] === '-') {
+ cssThemeVariables.get(parent).add(node)
+ }
+
+ // Track used CSS variables
+ if (node.value.includes('var(')) {
+ designSystem.trackUsedVariables(node.value)
+ }
+
+ // Track used animation names
+ if (node.property === 'animation') {
+ let parts = node.value.split(/\s+/)
+ for (let part of parts) usedKeyframeNames.add(part)
+ }
+
parent.push(node)
}
@@ -275,7 +302,7 @@ export function optimizeAst(ast: AstNode[]) {
if (node.selector === '&') {
for (let child of node.nodes) {
let nodes: AstNode[] = []
- transform(child, nodes, depth + 1)
+ transform(child, nodes, context, depth + 1)
if (nodes.length > 0) {
parent.push(...nodes)
}
@@ -286,7 +313,7 @@ export function optimizeAst(ast: AstNode[]) {
else {
let copy = { ...node, nodes: [] }
for (let child of node.nodes) {
- transform(child, copy.nodes, depth + 1)
+ transform(child, copy.nodes, context, depth + 1)
}
if (copy.nodes.length > 0) {
parent.push(copy)
@@ -305,7 +332,7 @@ export function optimizeAst(ast: AstNode[]) {
let copy = { ...node, nodes: [] }
for (let child of node.nodes) {
- transform(child, copy.nodes, depth + 1)
+ transform(child, copy.nodes, context, depth + 1)
}
parent.push(copy)
}
@@ -314,8 +341,15 @@ export function optimizeAst(ast: AstNode[]) {
else if (node.kind === 'at-rule') {
let copy = { ...node, nodes: [] }
for (let child of node.nodes) {
- transform(child, copy.nodes, depth + 1)
+ transform(child, copy.nodes, context, depth + 1)
}
+
+ // Only track `@keyframes` that could be removed, when they were defined
+ // inside of a `@theme`.
+ if (node.name === '@keyframes' && context.theme) {
+ keyframes.add(copy)
+ }
+
if (
copy.nodes.length > 0 ||
copy.name === '@layer' ||
@@ -332,7 +366,7 @@ export function optimizeAst(ast: AstNode[]) {
else if (node.kind === 'at-root') {
for (let child of node.nodes) {
let newParent: AstNode[] = []
- transform(child, newParent, 0)
+ transform(child, newParent, context, 0)
for (let child of newParent) {
atRoots.push(child)
}
@@ -347,7 +381,7 @@ export function optimizeAst(ast: AstNode[]) {
}
for (let child of node.nodes) {
- transform(child, parent, depth)
+ transform(child, parent, { ...context, ...node.context }, depth)
}
}
@@ -364,7 +398,52 @@ export function optimizeAst(ast: AstNode[]) {
let newAst: AstNode[] = []
for (let node of ast) {
- transform(node, newAst, 0)
+ transform(node, newAst, {}, 0)
+ }
+
+ // Remove unused theme variables
+ next: for (let [parent, declarations] of cssThemeVariables) {
+ for (let declaration of declarations) {
+ let options = designSystem.theme.getOptions(declaration.property)
+
+ if (options & (ThemeOptions.STATIC | ThemeOptions.USED)) {
+ if (declaration.property.startsWith('--animate-')) {
+ let parts = declaration.value!.split(/\s+/)
+ for (let part of parts) usedKeyframeNames.add(part)
+ }
+
+ continue
+ }
+
+ // Remove the declaration (from its parent)
+ let idx = parent.indexOf(declaration)
+ parent.splice(idx, 1)
+
+ // If the parent is now empty, remove it from the AST
+ if (parent.length === 0) {
+ for (let [idx, node] of newAst.entries()) {
+ // Assumption, but right now the `@theme` must be top-level, so we
+ // don't need to traverse the entire AST to find the parent.
+ //
+ // Checking for `rule`, because at this stage the `@theme` is already
+ // converted to a normal style rule `:root, :host`
+ if (node.kind === 'rule' && node.nodes === parent) {
+ newAst.splice(idx, 1)
+ break
+ }
+ }
+
+ continue next
+ }
+ }
+ }
+
+ // Remove unused keyframes
+ for (let keyframe of keyframes) {
+ if (!usedKeyframeNames.has(keyframe.params)) {
+ let idx = atRoots.indexOf(keyframe)
+ atRoots.splice(idx, 1)
+ }
}
return newAst.concat(atRoots)
diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts
index eb18cd734f21..55b91ec394e0 100644
--- a/packages/tailwindcss/src/compat/config.test.ts
+++ b/packages/tailwindcss/src/compat/config.test.ts
@@ -322,11 +322,7 @@ describe('theme callbacks', () => {
expect(compiler.build(['leading-base', 'leading-md', 'leading-xl', 'prose']))
.toMatchInlineSnapshot(`
- ":root, :host {
- --text-base: 100rem;
- --text-md--line-height: 101rem;
- }
- .prose {
+ ".prose {
[class~=lead-base] {
font-size: 100rem;
line-height: 201rem;
@@ -562,12 +558,7 @@ describe('default font family compatibility', () => {
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-font-family: Potato Sans;
- --default-font-feature-settings: normal;
- --default-font-variation-settings: normal;
- }
- .font-sans {
+ ".font-sans {
font-family: Potato Sans;
}
"
@@ -601,12 +592,7 @@ describe('default font family compatibility', () => {
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-font-family: Potato Sans;
- --default-font-feature-settings: "cv06";
- --default-font-variation-settings: normal;
- }
- .font-sans {
+ ".font-sans {
font-family: Potato Sans;
font-feature-settings: "cv06";
}
@@ -641,12 +627,7 @@ describe('default font family compatibility', () => {
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-font-family: Potato Sans;
- --default-font-feature-settings: normal;
- --default-font-variation-settings: "XHGT" 0.7;
- }
- .font-sans {
+ ".font-sans {
font-family: Potato Sans;
font-variation-settings: "XHGT" 0.7;
}
@@ -684,12 +665,7 @@ describe('default font family compatibility', () => {
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-font-family: Potato Sans;
- --default-font-feature-settings: "cv06";
- --default-font-variation-settings: "XHGT" 0.7;
- }
- .font-sans {
+ ".font-sans {
font-family: Potato Sans;
font-feature-settings: "cv06";
font-variation-settings: "XHGT" 0.7;
@@ -729,9 +705,6 @@ describe('default font family compatibility', () => {
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
":root, :host {
- --default-font-family: var(--font-family-sans);
- --default-font-feature-settings: var(--font-family-sans--font-feature-settings);
- --default-font-variation-settings: var(--font-family-sans--font-variation-settings);
--font-sans: Sandwich Sans;
}
.font-sans {
@@ -768,12 +741,7 @@ describe('default font family compatibility', () => {
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-font-family: Inter, system-ui, sans-serif;
- --default-font-feature-settings: normal;
- --default-font-variation-settings: normal;
- }
- .font-sans {
+ ".font-sans {
font-family: Inter, system-ui, sans-serif;
}
"
@@ -806,14 +774,7 @@ describe('default font family compatibility', () => {
}),
})
- expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-font-family: var(--font-family-sans);
- --default-font-feature-settings: var(--font-family-sans--font-feature-settings);
- --default-font-variation-settings: var(--font-family-sans--font-variation-settings);
- }
- "
- `)
+ expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`""`)
})
test('overriding `fontFamily.mono` sets `--default-mono-font-family`', async () => {
@@ -841,12 +802,7 @@ describe('default font family compatibility', () => {
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-mono-font-family: Potato Mono;
- --default-mono-font-feature-settings: normal;
- --default-mono-font-variation-settings: normal;
- }
- .font-mono {
+ ".font-mono {
font-family: Potato Mono;
}
"
@@ -880,12 +836,7 @@ describe('default font family compatibility', () => {
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-mono-font-family: Potato Mono;
- --default-mono-font-feature-settings: "cv06";
- --default-mono-font-variation-settings: normal;
- }
- .font-mono {
+ ".font-mono {
font-family: Potato Mono;
font-feature-settings: "cv06";
}
@@ -920,12 +871,7 @@ describe('default font family compatibility', () => {
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-mono-font-family: Potato Mono;
- --default-mono-font-feature-settings: normal;
- --default-mono-font-variation-settings: "XHGT" 0.7;
- }
- .font-mono {
+ ".font-mono {
font-family: Potato Mono;
font-variation-settings: "XHGT" 0.7;
}
@@ -963,12 +909,7 @@ describe('default font family compatibility', () => {
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-mono-font-family: Potato Mono;
- --default-mono-font-feature-settings: "cv06";
- --default-mono-font-variation-settings: "XHGT" 0.7;
- }
- .font-mono {
+ ".font-mono {
font-family: Potato Mono;
font-feature-settings: "cv06";
font-variation-settings: "XHGT" 0.7;
@@ -1008,9 +949,6 @@ describe('default font family compatibility', () => {
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
":root, :host {
- --default-mono-font-family: var(--font-mono);
- --default-mono-font-feature-settings: var(--font-mono--font-feature-settings);
- --default-mono-font-variation-settings: var(--font-mono--font-variation-settings);
--font-mono: Sandwich Mono;
}
.font-mono {
@@ -1046,14 +984,7 @@ describe('default font family compatibility', () => {
}),
})
- expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
- ":root, :host {
- --default-mono-font-family: var(--font-family-mono);
- --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
- --default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
- }
- "
- `)
+ expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`""`)
})
})
@@ -1179,13 +1110,7 @@ test('merges css breakpoints with js config screens', async () => {
expect(compiler.build(['sm:flex', 'md:flex', 'lg:flex', 'min-sm:max-md:underline']))
.toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-md: 50rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .sm\\:flex {
+ ".sm\\:flex {
@media (width >= 44rem) {
display: flex;
}
@@ -1333,12 +1258,7 @@ test('Prefixes configured in CSS take precedence over those defined in JS config
)
expect(compiler.build(['wat:custom'])).toMatchInlineSnapshot(`
- ":root, :host {
- --wat-color-red: #f00;
- --wat-color-green: #0f0;
- --wat-breakpoint-sm: 640px;
- }
- .wat\\:custom {
+ ".wat\\:custom {
color: red;
}
"
diff --git a/packages/tailwindcss/src/compat/container-config.test.ts b/packages/tailwindcss/src/compat/container-config.test.ts
index e2b9a0ca591f..b5618de84135 100644
--- a/packages/tailwindcss/src/compat/container-config.test.ts
+++ b/packages/tailwindcss/src/compat/container-config.test.ts
@@ -31,14 +31,7 @@ test('creates a custom utility to extend the built-in container', async () => {
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .container {
+ ".container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -96,14 +89,7 @@ test('allows padding to be defined at custom breakpoints', async () => {
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .container {
+ ".container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -164,14 +150,7 @@ test('allows breakpoints to be overwritten', async () => {
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .container {
+ ".container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -237,14 +216,7 @@ test('padding applies to custom `container` screens', async () => {
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .container {
+ ".container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -307,14 +279,7 @@ test("an empty `screen` config will undo all custom media screens and won't appl
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .container {
+ ".container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -380,20 +345,7 @@ test('legacy container component does not interfere with new --container variabl
expect(compiler.build(['max-w-sm'])).toMatchInlineSnapshot(`
":root, :host {
- --container-3xs: 16rem;
- --container-2xs: 18rem;
- --container-xs: 20rem;
--container-sm: 24rem;
- --container-md: 28rem;
- --container-lg: 32rem;
- --container-xl: 36rem;
- --container-2xl: 42rem;
- --container-3xl: 48rem;
- --container-4xl: 56rem;
- --container-5xl: 64rem;
- --container-6xl: 72rem;
- --container-7xl: 80rem;
- --container-prose: 65ch;
}
.max-w-sm {
max-width: var(--container-sm);
@@ -438,14 +390,7 @@ test('combines custom padding and screen overwrites', async () => {
})
expect(compiler.build(['container', '!container'])).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .\\!container {
+ ".\\!container {
width: 100% !important;
@media (width >= 40rem) {
max-width: 40rem !important;
@@ -557,14 +502,7 @@ test('filters out complex breakpoints', async () => {
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .container {
+ ".container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts
index 9eb65ccb954a..218a7eae1ea6 100644
--- a/packages/tailwindcss/src/compat/plugin-api.test.ts
+++ b/packages/tailwindcss/src/compat/plugin-api.test.ts
@@ -293,9 +293,6 @@ describe('theme', async () => {
.variable {
color: color-mix(in oklab, #ef4444 var(--opacity), transparent);
}
- :root, :host {
- --color-red-500: #ef4444;
- }
"
`)
})
@@ -379,9 +376,6 @@ describe('theme', async () => {
.js-variable {
color: color-mix(in oklab, rgb(255 0 0 / 1) var(--opacity), transparent);
}
- :root, :host {
- --color-custom-css: rgba(255 0 0 / );
- }
"
`)
})
@@ -1422,12 +1416,6 @@ describe('theme', async () => {
.my-width-2\\.5 {
width: 0.625rem;
}
- :root, :host {
- --width-1: 0.25rem;
- --width-1\\/2: 60%;
- --width-1\\.5: 0.375rem;
- --width-2_5: 0.625rem;
- }
"
`)
})
@@ -1479,12 +1467,6 @@ describe('theme', async () => {
.my-width-2\\.5 {
width: 0.625rem;
}
- :root, :host {
- --width-1: 0.25rem;
- --width-1\\/2: 60%;
- --width-1\\.5: 0.375rem;
- --width-2_5: 0.625rem;
- }
"
`)
})
diff --git a/packages/tailwindcss/src/compat/screens-config.test.ts b/packages/tailwindcss/src/compat/screens-config.test.ts
index e8f96ee55fd3..ac73d8216976 100644
--- a/packages/tailwindcss/src/compat/screens-config.test.ts
+++ b/packages/tailwindcss/src/compat/screens-config.test.ts
@@ -46,13 +46,7 @@ test('CSS `--breakpoint-*` merge with JS config `screens`', async () => {
'print:items-end',
]),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-md: 50rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .max-w-screen-sm {
+ ".max-w-screen-sm {
max-width: 44rem;
}
.sm\\:flex {
@@ -140,10 +134,7 @@ test('JS config `screens` extend CSS `--breakpoint-*`', async () => {
'print:items-end',
]),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-md: 50rem;
- }
- .min-xs\\:flex {
+ ".min-xs\\:flex {
@media (width >= 30rem) {
display: flex;
}
@@ -316,14 +307,7 @@ test('JS config `screens` overwrite CSS `--breakpoint-*`', async () => {
'print:items-end',
]),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- }
- .mini\\:flex {
+ ".mini\\:flex {
@media (width >= 40rem) {
display: flex;
}
@@ -584,10 +568,7 @@ describe('complex screen configs', () => {
'print:items-end',
]),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-md: 48rem;
- }
- .min-sm\\:flex {
+ ".min-sm\\:flex {
@media (width >= 40rem) {
display: flex;
}
diff --git a/packages/tailwindcss/src/css-functions.test.ts b/packages/tailwindcss/src/css-functions.test.ts
index 9d81b857ef77..4fa7ec540081 100644
--- a/packages/tailwindcss/src/css-functions.test.ts
+++ b/packages/tailwindcss/src/css-functions.test.ts
@@ -94,11 +94,7 @@ describe('--spacing(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --spacing: .25rem;
- }
-
- .foo {
+ ".foo {
margin: 1rem;
}"
`)
@@ -157,11 +153,7 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -197,11 +189,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -218,11 +206,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -239,11 +223,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -260,11 +240,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -281,11 +257,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -302,11 +274,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -323,11 +291,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: oklab(62.7955% .22486 .12584 / .75);
}"
`)
@@ -344,11 +308,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: oklab(62.7955% .22486 .12584 / .75);
}"
`)
@@ -365,11 +325,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: oklab(62.7955% .22486 .12584 / .75);
}"
`)
@@ -386,11 +342,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: color-mix(in oklab, red var(--opacity), transparent);
}"
`)
@@ -408,11 +360,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: color-mix(in oklab, red var(--opacity, 50%), transparent);
}"
`)
@@ -429,11 +377,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --spacing-12: 3rem;
- }
-
- .space-on-the-left {
+ ".space-on-the-left {
margin-left: 3rem;
}"
`)
@@ -450,11 +394,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --spacing-2_5: .625rem;
- }
-
- .space-on-the-left {
+ ".space-on-the-left {
margin-left: .625rem;
}"
`)
@@ -471,11 +411,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --spacing-2_5: .625rem;
- }
-
- .space-on-the-left {
+ ".space-on-the-left {
margin-left: calc(100vh - .625rem);
}"
`)
@@ -492,11 +428,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --radius-lg: .5rem;
- }
-
- .radius {
+ ".radius {
border-radius: .5rem;
}"
`)
@@ -514,11 +446,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --blur: 8px;
- }
-
- .default-blur {
+ ".default-blur {
filter: blur(8px);
}"
`)
@@ -537,12 +465,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --text-xs: 1337.75rem;
- --text-xs--line-height: 1337rem;
- }
-
- .text {
+ ".text {
font-size: 1337.75rem;
line-height: 1337rem;
}"
@@ -626,11 +549,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: oklab(62.7955% .22486 .12584 / .25);
}"
`)
@@ -684,12 +603,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- --color-foo: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -707,12 +621,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- --color-foo: oklab(62.7955% .22486 .12584 / .5);
- }
-
- .red {
+ ".red {
color: oklab(62.7955% .22486 .12584 / .25);
}"
`)
@@ -731,11 +640,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -752,11 +657,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: oklab(62.7955% .22486 .12584 / .5);
}"
`)
@@ -773,11 +674,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -796,11 +693,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --blur: 8px;
- }
-
- .blur {
+ ".blur {
filter: blur(8px);
}"
`)
@@ -859,11 +752,6 @@ describe('theme(…)', () => {
.sm\\:\\[--color\\:theme\\(colors\\.red\\[500\\]\\)\\] {
--color: red;
}
- }
-
- :root, :host {
- --breakpoint-sm: 40rem;
- --color-red-500: red;
}"
`)
})
@@ -921,12 +809,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- }
-
- @media (width >= 48rem) and (width <= 64rem) {
+ "@media (width >= 48rem) and (width <= 64rem) {
.red {
color: red;
}
@@ -948,12 +831,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- }
-
- @media (width >= 48rem) and (width < 64rem) {
+ "@media (width >= 48rem) and (width < 64rem) {
.red {
color: red;
}
@@ -976,11 +854,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-md: 48rem;
- }
-
- @media (width >= 48rem) {
+ "@media (width >= 48rem) {
.red {
color: red;
}
@@ -1001,11 +875,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-md: 48rem;
- }
-
- @container (width > 48rem) {
+ "@container (width > 48rem) {
.red {
color: red;
}
@@ -1026,11 +896,7 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
- ":root, :host {
- --font-size-xs: .75rem;
- }
-
- @supports (text-stroke: 0.75rem) {
+ "@supports (text-stroke: 0.75rem) {
.red {
color: red;
}
@@ -1187,11 +1053,7 @@ test('replaces CSS theme() function with values inside imported stylesheets', as
},
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-red-500: red;
- }
-
- .red {
+ ".red {
color: red;
}"
`)
@@ -1212,11 +1074,7 @@ test('resolves paths ending with a 1', async () => {
[],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --spacing-1: .25rem;
- }
-
- .foo {
+ ".foo {
margin: .25rem;
}"
`)
diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts
index a414f1d23848..cfa02beaf366 100644
--- a/packages/tailwindcss/src/design-system.ts
+++ b/packages/tailwindcss/src/design-system.ts
@@ -7,6 +7,7 @@ import { getClassOrder } from './sort'
import type { Theme, ThemeKey } from './theme'
import { Utilities, createUtilities, withAlpha } from './utilities'
import { DefaultMap } from './utils/default-map'
+import * as ValueParser from './value-parser'
import { Variants, createVariants } from './variants'
export type DesignSystem = {
@@ -30,6 +31,8 @@ export type DesignSystem = {
getVariantOrder(): Map
resolveThemeValue(path: string): string | undefined
+ trackUsedVariables(raw: string): void
+
// Used by IntelliSense
candidatesToCss(classes: string[]): (string | null)[]
}
@@ -42,6 +45,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
let parsedCandidates = new DefaultMap((candidate) =>
Array.from(parseCandidate(candidate, designSystem)),
)
+
let compiledAstNodes = new DefaultMap((candidate) => {
let ast = compileAstNodes(candidate, designSystem)
@@ -64,6 +68,22 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
return ast
})
+ let trackUsedVariables = new DefaultMap((raw) => {
+ ValueParser.walk(ValueParser.parse(raw), (node) => {
+ if (node.kind !== 'function' || node.value !== 'var') return
+
+ ValueParser.walk(node.nodes, (child) => {
+ if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return
+
+ theme.markUsedVariable(child.value)
+ })
+
+ return ValueParser.ValueWalkAction.Skip
+ })
+
+ return true
+ })
+
let designSystem: DesignSystem = {
theme,
utilities,
@@ -84,7 +104,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
},
})
- astNodes = optimizeAst(astNodes)
+ astNodes = optimizeAst(astNodes, designSystem)
if (astNodes.length === 0 || wasInvalid) {
result.push(null)
@@ -159,6 +179,10 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
return themeValue
},
+
+ trackUsedVariables(raw: string) {
+ trackUsedVariables.get(raw)
+ },
}
return designSystem
diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts
index 497ccc034173..f7c2084d6995 100644
--- a/packages/tailwindcss/src/index.test.ts
+++ b/packages/tailwindcss/src/index.test.ts
@@ -27,7 +27,6 @@ describe('compiling CSS', () => {
).toMatchInlineSnapshot(`
":root, :host {
--color-black: #000;
- --breakpoint-md: 768px;
}
@layer utilities {
@@ -173,7 +172,6 @@ describe('compiling CSS', () => {
--spacing-1\\.5: 1.5px;
--spacing-2_5: 2.5px;
--spacing-3\\.5: 3.5px;
- --spacing-3_5: 3.5px;
--spacing-foo\\/bar: 3rem;
}
@@ -294,7 +292,6 @@ describe('@apply', () => {
--color-blue-500: #3b82f6;
--color-green-200: #bbf7d0;
--color-green-500: #22c55e;
- --breakpoint-md: 768px;
--animate-spin: spin 1s linear infinite;
}
@@ -1150,7 +1147,6 @@ describe('Parsing themes values from CSS', () => {
).toMatchInlineSnapshot(`
":root, :host {
--color-red: red;
- --animate-foo: foo 1s infinite;
--text-lg: 20px;
}
@@ -1160,12 +1156,6 @@ describe('Parsing themes values from CSS', () => {
.accent-red {
accent-color: var(--color-red);
- }
-
- @keyframes foo {
- to {
- opacity: 1;
- }
}"
`)
})
@@ -1392,7 +1382,6 @@ describe('Parsing themes values from CSS', () => {
),
).toMatchInlineSnapshot(`
":root, :host {
- --inset-shadow-sm: inset 0 2px 4px #0000000d;
--inset-md: 50px;
}
@@ -1583,6 +1572,219 @@ describe('Parsing themes values from CSS', () => {
`)
})
+ test('keyframes are generated when used in an animation', async () => {
+ expect(
+ await compileCss(
+ css`
+ @theme {
+ --animate-foo: used 1s infinite;
+ --animate-bar: unused 1s infinite;
+
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes unused {
+ to {
+ opacity: 0;
+ }
+ }
+ }
+
+ @tailwind utilities;
+ `,
+ ['animate-foo'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root, :host {
+ --animate-foo: used 1s infinite;
+ }
+
+ .animate-foo {
+ animation: var(--animate-foo);
+ }
+
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }"
+ `)
+ })
+
+ test('keyframes are generated when used in an animation using `@theme inline`', async () => {
+ expect(
+ await compileCss(
+ css`
+ @theme inline {
+ --animate-foo: used 1s infinite;
+ --animate-bar: unused 1s infinite;
+
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes unused {
+ to {
+ opacity: 0;
+ }
+ }
+ }
+
+ @tailwind utilities;
+ `,
+ ['animate-foo'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ".animate-foo {
+ animation: 1s infinite used;
+ }
+
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }"
+ `)
+ })
+
+ test('keyframes are generated when used in an animation using `@theme static`', async () => {
+ expect(
+ await compileCss(
+ css`
+ @theme static {
+ --animate-foo: used 1s infinite;
+ --animate-bar: unused-but-kept 1s infinite;
+
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes unused-but-kept {
+ to {
+ opacity: 0;
+ }
+ }
+ }
+
+ @tailwind utilities;
+ `,
+ ['animate-foo'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root, :host {
+ --animate-foo: used 1s infinite;
+ --animate-bar: unused-but-kept 1s infinite;
+ }
+
+ .animate-foo {
+ animation: var(--animate-foo);
+ }
+
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes unused-but-kept {
+ to {
+ opacity: 0;
+ }
+ }"
+ `)
+ })
+
+ test('keyframes are generated when used in user CSS', async () => {
+ expect(
+ await compileCss(
+ css`
+ @theme {
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes unused {
+ to {
+ opacity: 0;
+ }
+ }
+ }
+
+ .foo {
+ animation: used 1s infinite;
+ }
+
+ @tailwind utilities;
+ `,
+ [],
+ ),
+ ).toMatchInlineSnapshot(`
+ ".foo {
+ animation: 1s infinite used;
+ }
+
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }"
+ `)
+ })
+
+ test('keyframes outside of `@theme are always preserved', async () => {
+ expect(
+ await compileCss(
+ css`
+ @theme {
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }
+ }
+
+ @keyframes unused {
+ to {
+ opacity: 0;
+ }
+ }
+
+ .foo {
+ animation: used 1s infinite;
+ }
+
+ @tailwind utilities;
+ `,
+ [],
+ ),
+ ).toMatchInlineSnapshot(`
+ "@keyframes unused {
+ to {
+ opacity: 0;
+ }
+ }
+
+ .foo {
+ animation: 1s infinite used;
+ }
+
+ @keyframes used {
+ to {
+ opacity: 1;
+ }
+ }"
+ `)
+ })
+
test('theme values added as reference are not included in the output as variables', async () => {
expect(
await compileCss(
@@ -1798,13 +2000,7 @@ describe('Parsing themes values from CSS', () => {
['bg-tomato', 'bg-potato', 'bg-primary'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-tomato: #e10c04;
- --color-potato: #ac855b;
- --color-primary: var(--primary);
- }
-
- .bg-potato {
+ ".bg-potato {
background-color: #ac855b;
}
@@ -1818,6 +2014,33 @@ describe('Parsing themes values from CSS', () => {
`)
})
+ test('theme values added as `static` will always be generated, regardless of whether they were used or not', async () => {
+ expect(
+ await compileCss(
+ css`
+ @theme static {
+ --color-tomato: #e10c04;
+ --color-potato: #ac855b;
+ --color-primary: var(--primary);
+ }
+
+ @tailwind utilities;
+ `,
+ ['bg-tomato'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root, :host {
+ --color-tomato: #e10c04;
+ --color-potato: #ac855b;
+ --color-primary: var(--primary);
+ }
+
+ .bg-tomato {
+ background-color: var(--color-tomato);
+ }"
+ `)
+ })
+
test('wrapping `@theme` with `@media theme(inline)` behaves like `@theme inline` to support `@import` statements', async () => {
expect(
await compileCss(
@@ -1835,13 +2058,7 @@ describe('Parsing themes values from CSS', () => {
['bg-tomato', 'bg-potato', 'bg-primary'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-tomato: #e10c04;
- --color-potato: #ac855b;
- --color-primary: var(--primary);
- }
-
- .bg-potato {
+ ".bg-potato {
background-color: #ac855b;
}
@@ -1954,11 +2171,7 @@ describe('Parsing themes values from CSS', () => {
['bg-potato'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --color-potato: #efb46b;
- }
-
- .bg-potato {
+ ".bg-potato {
background-color: #efb46b;
}"
`)
diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts
index 14bcee2fe104..07956ab421ce 100644
--- a/packages/tailwindcss/src/index.ts
+++ b/packages/tailwindcss/src/index.ts
@@ -4,6 +4,7 @@ import {
atRoot,
atRule,
comment,
+ context,
context as contextNode,
decl,
optimizeAst,
@@ -63,6 +64,8 @@ function parseThemeOptions(params: string) {
options |= ThemeOptions.INLINE
} else if (option === 'default') {
options |= ThemeOptions.DEFAULT
+ } else if (option === 'static') {
+ options |= ThemeOptions.STATIC
} else if (option.startsWith('prefix(') && option.endsWith(')')) {
prefix = option.slice(7, -1)
}
@@ -454,18 +457,16 @@ async function parseCss(
}
// Record all custom properties in the `@theme` declaration
- walk(node.nodes, (child, { replaceWith }) => {
+ walk(node.nodes, (child) => {
// Collect `@keyframes` rules to re-insert with theme variables later,
// since the `@theme` rule itself will be removed.
if (child.kind === 'at-rule' && child.name === '@keyframes') {
// Do not track/emit `@keyframes`, if they are part of a `@theme reference`.
if (themeOptions & ThemeOptions.REFERENCE) {
- replaceWith([])
return WalkAction.Skip
}
theme.addKeyframes(child)
- replaceWith([])
return WalkAction.Skip
}
@@ -488,7 +489,7 @@ async function parseCss(
// Keep a reference to the first `@theme` rule to update with the full
// theme later, and delete any other `@theme` rules.
if (!firstThemeRule && !(themeOptions & ThemeOptions.REFERENCE)) {
- firstThemeRule = styleRule(':root, :host', node.nodes)
+ firstThemeRule = styleRule(':root, :host', [])
replaceWith([firstThemeRule])
} else {
replaceWith([])
@@ -523,35 +524,25 @@ async function parseCss(
customUtility(designSystem)
}
- // Output final set of theme variables at the position of the first `@theme`
- // rule.
+ // Output final set of theme variables at the position of the first
+ // `@theme` rule.
if (firstThemeRule) {
let nodes = []
- for (let [key, value] of theme.entries()) {
+ for (let [key, value] of designSystem.theme.entries()) {
if (value.options & ThemeOptions.REFERENCE) continue
+
nodes.push(decl(escape(key), value.value))
}
- let keyframesRules = theme.getKeyframes()
- if (keyframesRules.length > 0) {
- let animationParts = [...theme.namespace('--animate').values()].flatMap((animation) =>
- animation.split(/\s+/),
- )
-
- for (let keyframesRule of keyframesRules) {
- // Remove any keyframes that aren't used by an animation variable.
- let keyframesName = keyframesRule.params
- if (!animationParts.includes(keyframesName)) {
- continue
- }
-
- // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when
- // printing.
- nodes.push(atRoot([keyframesRule]))
- }
+ let keyframesRules = designSystem.theme.getKeyframes()
+ for (let keyframes of keyframesRules) {
+ // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when
+ // printing.
+ nodes.push(atRoot([keyframes]))
}
- firstThemeRule.nodes = nodes
+
+ firstThemeRule.nodes = [context({ theme: true }, nodes)]
}
// Replace the `@tailwind utilities` node with a context since it prints
@@ -650,7 +641,7 @@ export async function compileAst(
}
if (!utilitiesNode) {
- compiled ??= optimizeAst(ast)
+ compiled ??= optimizeAst(ast, designSystem)
return compiled
}
@@ -660,7 +651,11 @@ export async function compileAst(
let prevSize = allValidCandidates.size
for (let candidate of newRawCandidates) {
if (!designSystem.invalidCandidates.has(candidate)) {
- allValidCandidates.add(candidate)
+ if (candidate[0] === '-' && candidate[1] === '-') {
+ designSystem.theme.markUsedVariable(candidate)
+ } else {
+ allValidCandidates.add(candidate)
+ }
didChange ||= allValidCandidates.size !== prevSize
}
}
@@ -668,7 +663,7 @@ export async function compileAst(
// If no new candidates were added, we can return the original CSS. This
// currently assumes that we only add new candidates and never remove any.
if (!didChange) {
- compiled ??= optimizeAst(ast)
+ compiled ??= optimizeAst(ast, designSystem)
return compiled
}
@@ -680,7 +675,7 @@ export async function compileAst(
// CSS. This currently assumes that we only add new ast nodes and never
// remove any.
if (previousAstNodeCount === newNodes.length) {
- compiled ??= optimizeAst(ast)
+ compiled ??= optimizeAst(ast, designSystem)
return compiled
}
@@ -688,7 +683,7 @@ export async function compileAst(
utilitiesNode.nodes = newNodes
- compiled = optimizeAst(ast)
+ compiled = optimizeAst(ast, designSystem)
return compiled
},
}
diff --git a/packages/tailwindcss/src/prefix.test.ts b/packages/tailwindcss/src/prefix.test.ts
index 8fb0eec6a48f..020575ce2f87 100644
--- a/packages/tailwindcss/src/prefix.test.ts
+++ b/packages/tailwindcss/src/prefix.test.ts
@@ -108,8 +108,6 @@ test('CSS variables output by the theme are prefixed', async () => {
expect(compiler.build(['tw:text-red'])).toMatchInlineSnapshot(`
":root, :host {
--tw-color-red: #f00;
- --tw-color-green: #0f0;
- --tw-breakpoint-sm: 640px;
}
.tw\\:text-red {
color: var(--tw-color-red);
@@ -131,12 +129,7 @@ test('CSS theme functions do not use the prefix', async () => {
expect(compiler.build(['tw:[color:theme(--color-red)]', 'tw:text-[theme(--color-red)]']))
.toMatchInlineSnapshot(`
- ":root, :host {
- --tw-color-red: #f00;
- --tw-color-green: #0f0;
- --tw-breakpoint-sm: 640px;
- }
- .tw\\:\\[color\\:theme\\(--color-red\\)\\] {
+ ".tw\\:\\[color\\:theme\\(--color-red\\)\\] {
color: #f00;
}
.tw\\:text-\\[theme\\(--color-red\\)\\] {
@@ -193,12 +186,7 @@ test('JS theme functions do not use the prefix', async () => {
)
expect(compiler.build(['tw:my-custom'])).toMatchInlineSnapshot(`
- ":root, :host {
- --tw-color-red: #f00;
- --tw-color-green: #0f0;
- --tw-breakpoint-sm: 640px;
- }
- .tw\\:my-custom {
+ ".tw\\:my-custom {
color: #f00;
}
"
@@ -337,12 +325,7 @@ test('a prefix can be configured via @import prefix(…)', async () => {
},
})
- expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(`
- ":root, :host {
- --tw-color-potato: #7a4724;
- }
- "
- `)
+ expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(`""`)
})
test('a prefix must be letters only', async () => {
diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts
index 0b0ca96c8d11..0cc80f3b82fc 100644
--- a/packages/tailwindcss/src/theme.ts
+++ b/packages/tailwindcss/src/theme.ts
@@ -1,11 +1,14 @@
-import type { AtRule } from './ast'
-import { escape } from './utils/escape'
+import { type AtRule } from './ast'
+import { escape, unescape } from './utils/escape'
export const enum ThemeOptions {
NONE = 0,
INLINE = 1 << 0,
REFERENCE = 1 << 1,
DEFAULT = 1 << 2,
+
+ STATIC = 1 << 3,
+ USED = 1 << 4,
}
// In the future we may want to replace this with just a `Set` of known theme
@@ -106,6 +109,7 @@ export class Theme {
}
getOptions(key: string) {
+ key = unescape(this.#unprefixKey(key))
return this.values.get(key)?.options ?? ThemeOptions.NONE
}
@@ -123,6 +127,11 @@ export class Theme {
return `--${this.prefix}-${key.slice(2)}`
}
+ #unprefixKey(key: string) {
+ if (!this.prefix) return key
+ return `--${key.slice(3 + this.prefix.length)}`
+ }
+
clearNamespace(namespace: string, clearOptions: ThemeOptions) {
let ignored = ignoredThemeKeyMap.get(namespace) ?? []
@@ -175,6 +184,13 @@ export class Theme {
return `var(${escape(this.#prefixKey(themeKey))})`
}
+ markUsedVariable(themeKey: string) {
+ let key = unescape(this.#unprefixKey(themeKey))
+ let value = this.values.get(key)
+ if (!value) return
+ value.options |= ThemeOptions.USED
+ }
+
resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null {
let themeKey = this.#resolveKey(candidateValue, themeKeys)
diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts
index 4da13ed6469d..56eaca4f880c 100644
--- a/packages/tailwindcss/src/utilities.test.ts
+++ b/packages/tailwindcss/src/utilities.test.ts
@@ -147,7 +147,6 @@ test('inset', async () => {
).toMatchInlineSnapshot(`
":root, :host {
--spacing-4: 1rem;
- --inset-shadow-sm: inset 0 1px 1px #0000000d;
--inset-shadowned: 1940px;
}
@@ -3162,10 +3161,6 @@ describe('container', () => {
).toMatchInlineSnapshot(`
":root, :host {
--breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
}
.container {
@@ -3305,10 +3300,6 @@ describe('container', () => {
).toMatchInlineSnapshot(`
":root, :host {
--breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
}
.container {
@@ -4045,11 +4036,7 @@ test('translate-x', async () => {
['translate-x-full', '-translate-x-full', 'translate-x-px', '-translate-x-[var(--value)]'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --spacing: .25rem;
- }
-
- .-translate-x-\\[var\\(--value\\)\\] {
+ ".-translate-x-\\[var\\(--value\\)\\] {
--tw-translate-x: calc(var(--value) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
@@ -4173,11 +4160,7 @@ test('translate-y', async () => {
['translate-y-full', '-translate-y-full', 'translate-y-px', '-translate-y-[var(--value)]'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --spacing: .25rem;
- }
-
- .-translate-y-\\[var\\(--value\\)\\] {
+ ".-translate-y-\\[var\\(--value\\)\\] {
--tw-translate-y: calc(var(--value) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
@@ -7734,11 +7717,7 @@ test('divide-x with custom default border width', async () => {
['divide-x'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --default-border-width: 2px;
- }
-
- :where(.divide-x > :not(:last-child)) {
+ ":where(.divide-x > :not(:last-child)) {
--tw-divide-x-reverse: 0;
border-inline-style: var(--tw-border-style);
border-inline-start-width: calc(2px * var(--tw-divide-x-reverse));
@@ -7840,11 +7819,7 @@ test('divide-y with custom default border width', async () => {
['divide-y'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --default-border-width: 2px;
- }
-
- :where(.divide-y > :not(:last-child)) {
+ ":where(.divide-y > :not(:last-child)) {
--tw-divide-y-reverse: 0;
border-bottom-style: var(--tw-border-style);
border-top-style: var(--tw-border-style);
@@ -9930,11 +9905,7 @@ test('border with custom default border width', async () => {
['border'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --default-border-width: 2px;
- }
-
- .border {
+ ".border {
border-style: var(--tw-border-style);
border-width: 2px;
}
@@ -13870,7 +13841,6 @@ test('transition', async () => {
":root, :host {
--default-transition-timing-function: ease;
--default-transition-duration: .1s;
- --transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
--transition-property-opacity: opacity;
}
@@ -13936,12 +13906,7 @@ test('transition', async () => {
['transition', 'transition-all', 'transition-colors'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --default-transition-timing-function: ease;
- --default-transition-duration: .1s;
- }
-
- .transition {
+ ".transition {
transition-property: 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;
transition-timing-function: var(--tw-ease, ease);
transition-duration: var(--tw-duration, .1s);
@@ -15227,8 +15192,6 @@ test('shadow', async () => {
).toMatchInlineSnapshot(`
":root, :host {
--color-red-500: #ef4444;
- --shadow-sm: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --shadow-xl: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
}
.shadow-\\[10px_10px\\] {
@@ -15451,8 +15414,6 @@ test('inset-shadow', async () => {
).toMatchInlineSnapshot(`
":root, :host {
--color-red-500: #ef4444;
- --inset-shadow: inset 0 2px 4px #0000000d;
- --inset-shadow-sm: inset 0 1px 1px #0000000d;
}
.inset-shadow {
@@ -16443,7 +16404,6 @@ describe('spacing utilities', () => {
expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`
":root, :host {
--spacing-sm: 8px;
- --container-sm: 256px;
}
.w-sm {
@@ -16500,6 +16460,40 @@ describe('custom utilities', () => {
`)
})
+ test('custom static utility emit CSS variables if the utility is used', async () => {
+ let { build } = await compile(css`
+ @layer utilities {
+ @tailwind utilities;
+ }
+
+ @theme {
+ --example-foo: 123px;
+ }
+
+ @utility foo {
+ value: var(--example-foo);
+ }
+ `)
+ let compiled = build([])
+
+ // `foo` is not used yet:
+ expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`"@layer utilities;"`)
+
+ // `foo` is used, and the CSS variable is emitted:
+ compiled = build(['foo'])
+ expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`
+ "@layer utilities {
+ .foo {
+ value: var(--example-foo);
+ }
+ }
+
+ :root, :host {
+ --example-foo: 123px;
+ }"
+ `)
+ })
+
test('custom static utility (negative)', async () => {
let { build } = await compile(css`
@layer utilities {
@@ -17650,6 +17644,56 @@ describe('custom utilities', () => {
`)
expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('')
})
+
+ test('variables used in `@utility` will not be emitted if the utility is not used', async () => {
+ let input = css`
+ @theme {
+ --example-foo: red;
+ --color-red-500: #f00;
+ }
+
+ @utility example-* {
+ color: var(--color-red-500);
+ background-color: --value(--example);
+ }
+
+ @tailwind utilities;
+ `
+
+ expect(await compileCss(input, ['flex'])).toMatchInlineSnapshot(`
+ ".flex {
+ display: flex;
+ }"
+ `)
+ })
+
+ test('variables used in `@utility` will be emitted if the utility is used', async () => {
+ let input = css`
+ @theme {
+ --example-foo: red;
+ --color-red-500: #f00;
+ }
+
+ @utility example-* {
+ color: var(--color-red-500);
+ background-color: --value(--example);
+ }
+
+ @tailwind utilities;
+ `
+
+ expect(await compileCss(input, ['example-foo'])).toMatchInlineSnapshot(`
+ ":root, :host {
+ --example-foo: red;
+ --color-red-500: red;
+ }
+
+ .example-foo {
+ color: var(--color-red-500);
+ background-color: var(--example-foo);
+ }"
+ `)
+ })
})
test('resolve value based on `@theme`', async () => {
@@ -17710,11 +17754,7 @@ describe('custom utilities', () => {
`
expect(await compileCss(input, ['tab-github'])).toMatchInlineSnapshot(`
- ":root, :host {
- --tab-size-github: 8;
- }
-
- .tab-github {
+ ".tab-github {
tab-size: 8;
}"
`)
diff --git a/packages/tailwindcss/src/value-parser.ts b/packages/tailwindcss/src/value-parser.ts
index dda1a7d80ab7..07281f95d095 100644
--- a/packages/tailwindcss/src/value-parser.ts
+++ b/packages/tailwindcss/src/value-parser.ts
@@ -163,6 +163,14 @@ export function parse(input: string) {
let currentChar = input.charCodeAt(i)
switch (currentChar) {
+ // Current character is a `\` therefore the next character is escaped,
+ // consume it together with the next character and continue.
+ case BACKSLASH: {
+ buffer += input[i] + input[i + 1]
+ i++
+ break
+ }
+
// Space and commas are bundled into separators
//
// E.g.:
diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts
index be374dbd9196..8348f940063b 100644
--- a/packages/tailwindcss/src/variants.test.ts
+++ b/packages/tailwindcss/src/variants.test.ts
@@ -747,15 +747,7 @@ test('default breakpoints', async () => {
['sm:flex', 'md:flex', 'lg:flex', 'xl:flex', '2xl:flex'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 640px;
- --breakpoint-md: 768px;
- --breakpoint-lg: 1024px;
- --breakpoint-xl: 1280px;
- --breakpoint-2xl: 1536px;
- }
-
- @media (width >= 640px) {
+ "@media (width >= 640px) {
.sm\\:flex {
display: flex;
}
@@ -815,11 +807,7 @@ test('custom breakpoint', async () => {
['10xl:flex'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-10xl: 5000px;
- }
-
- @media (width >= 5000px) {
+ "@media (width >= 5000px) {
.\\31 0xl\\:flex {
display: flex;
}
@@ -842,13 +830,7 @@ test('max-*', async () => {
['max-lg:flex', 'max-sm:flex', 'max-md:flex'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 640px;
- --breakpoint-lg: 1024px;
- --breakpoint-md: 768px;
- }
-
- @media (width < 1024px) {
+ "@media (width < 1024px) {
.max-lg\\:flex {
display: flex;
}
@@ -897,13 +879,7 @@ test('min-*', async () => {
['min-lg:flex', 'min-sm:flex', 'min-md:flex'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 640px;
- --breakpoint-lg: 1024px;
- --breakpoint-md: 768px;
- }
-
- @media (width >= 640px) {
+ "@media (width >= 640px) {
.min-sm\\:flex {
display: flex;
}
@@ -954,15 +930,7 @@ test('sorting stacked min-* and max-* variants', async () => {
['min-sm:max-lg:flex', 'min-sm:max-xl:flex', 'min-md:max-lg:flex', 'min-xs:max-sm:flex'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 640px;
- --breakpoint-lg: 1024px;
- --breakpoint-md: 768px;
- --breakpoint-xl: 1280px;
- --breakpoint-xs: 280px;
- }
-
- @media (width >= 280px) {
+ "@media (width >= 280px) {
@media (width < 640px) {
.min-xs\\:max-sm\\:flex {
display: flex;
@@ -1009,13 +977,7 @@ test('stacked min-* and max-* variants should come after unprefixed variants', a
['sm:flex', 'min-sm:max-lg:flex', 'md:flex', 'min-md:max-lg:flex'],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 640px;
- --breakpoint-lg: 1024px;
- --breakpoint-md: 768px;
- }
-
- @media (width >= 640px) {
+ "@media (width >= 640px) {
.sm\\:flex {
display: flex;
}
@@ -1071,13 +1033,7 @@ test('min, max and unprefixed breakpoints', async () => {
],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 640px;
- --breakpoint-lg: 1024px;
- --breakpoint-md: 768px;
- }
-
- @media (width < 1024px) {
+ "@media (width < 1024px) {
.max-lg\\:flex {
display: flex;
}
@@ -1456,11 +1412,7 @@ test('not', async () => {
],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 640px;
- }
-
- .not-first\\:flex:not(:first-child), .not-last\\:flex:not(:last-child), .not-only\\:flex:not(:only-child), .not-odd\\:flex:not(:nth-child(odd)), .not-even\\:flex:not(:nth-child(2n)), .not-first-of-type\\:flex:not(:first-of-type), .not-last-of-type\\:flex:not(:last-of-type), .not-only-of-type\\:flex:not(:only-of-type), .not-visited\\:flex:not(:visited), .not-target\\:flex:not(:target) {
+ ".not-first\\:flex:not(:first-child), .not-last\\:flex:not(:last-child), .not-only\\:flex:not(:only-child), .not-odd\\:flex:not(:nth-child(odd)), .not-even\\:flex:not(:nth-child(2n)), .not-first-of-type\\:flex:not(:first-of-type), .not-last-of-type\\:flex:not(:last-of-type), .not-only-of-type\\:flex:not(:only-of-type), .not-visited\\:flex:not(:visited), .not-target\\:flex:not(:target) {
display: flex;
}
@@ -2002,11 +1954,7 @@ test('container queries', async () => {
],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --container-lg: 1024px;
- }
-
- @container name (width < 1024px) {
+ "@container name (width < 1024px) {
.\\@max-lg\\/name\\:flex {
display: flex;
}
@@ -2158,15 +2106,7 @@ test('variant order', async () => {
],
),
).toMatchInlineSnapshot(`
- ":root, :host {
- --breakpoint-sm: 640px;
- --breakpoint-md: 768px;
- --breakpoint-lg: 1024px;
- --breakpoint-xl: 1280px;
- --breakpoint-2xl: 1536px;
- }
-
- @media (hover: hover) {
+ "@media (hover: hover) {
.group-hover\\:flex:is(:where(.group):hover *), .peer-hover\\:flex:is(:where(.peer):hover ~ *) {
display: flex;
}