From ea249957497c095ff8d228a3c913f608afbb9b28 Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Wed, 29 Jan 2025 20:56:23 +0100
Subject: [PATCH 01/15] Only generate positive `grid-cols-*` and `grid-rows-*`
utilities (#16020)
This PR fixes an issue where `grid-cols-0` and `grid-rows-0` generated
invalid CSS. We now ensure that the value is any positive integer
(except 0).
Fixes: #16012
---
CHANGELOG.md | 4 +++-
packages/tailwindcss/src/utilities.test.ts | 2 ++
packages/tailwindcss/src/utilities.ts | 5 +++--
packages/tailwindcss/src/utils/infer-data-type.ts | 5 +++++
4 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c60d4490700e..e3481c52a7ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
-- Nothing yet!
+### Fixed
+
+- Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020))
## [4.0.1] - 2025-01-29
diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts
index 8e40d3816317..d57bffeaabee 100644
--- a/packages/tailwindcss/src/utilities.test.ts
+++ b/packages/tailwindcss/src/utilities.test.ts
@@ -6995,6 +6995,7 @@ test('grid-cols', async () => {
expect(
await run([
'grid-cols',
+ 'grid-cols-0',
'-grid-cols-none',
'-grid-cols-subgrid',
'grid-cols--12',
@@ -7043,6 +7044,7 @@ test('grid-rows', async () => {
expect(
await run([
'grid-rows',
+ 'grid-rows-0',
'-grid-rows-none',
'-grid-rows-subgrid',
'grid-rows--12',
diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts
index 2fb717fae418..57f39e021358 100644
--- a/packages/tailwindcss/src/utilities.ts
+++ b/packages/tailwindcss/src/utilities.ts
@@ -17,6 +17,7 @@ import { DefaultMap } from './utils/default-map'
import {
inferDataType,
isPositiveInteger,
+ isStrictPositiveInteger,
isValidOpacityValue,
isValidSpacingMultiplier,
} from './utils/infer-data-type'
@@ -1752,7 +1753,7 @@ export function createUtilities(theme: Theme) {
functionalUtility('grid-cols', {
themeKeys: ['--grid-template-columns'],
handleBareValue: ({ value }) => {
- if (!isPositiveInteger(value)) return null
+ if (!isStrictPositiveInteger(value)) return null
return `repeat(${value}, minmax(0, 1fr))`
},
handle: (value) => [decl('grid-template-columns', value)],
@@ -1763,7 +1764,7 @@ export function createUtilities(theme: Theme) {
functionalUtility('grid-rows', {
themeKeys: ['--grid-template-rows'],
handleBareValue: ({ value }) => {
- if (!isPositiveInteger(value)) return null
+ if (!isStrictPositiveInteger(value)) return null
return `repeat(${value}, minmax(0, 1fr))`
},
handle: (value) => [decl('grid-template-rows', value)],
diff --git a/packages/tailwindcss/src/utils/infer-data-type.ts b/packages/tailwindcss/src/utils/infer-data-type.ts
index 56ab9c60cdde..9496986557e5 100644
--- a/packages/tailwindcss/src/utils/infer-data-type.ts
+++ b/packages/tailwindcss/src/utils/infer-data-type.ts
@@ -341,6 +341,11 @@ export function isPositiveInteger(value: any) {
return Number.isInteger(num) && num >= 0 && String(num) === String(value)
}
+export function isStrictPositiveInteger(value: any) {
+ let num = Number(value)
+ return Number.isInteger(num) && num > 0 && String(num) === String(value)
+}
+
export function isValidSpacingMultiplier(value: any) {
return isMultipleOf(value, 0.25)
}
From 0d5e2be3125bd0d2fda7f36d2f30d078361d83c7 Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Thu, 30 Jan 2025 16:29:08 +0100
Subject: [PATCH 02/15] Ensure we process Tailwind CSS features when using
`@reference` (#16057)
This PR fixes an issue where if you only used `@reference` that we
didn't process Tailwind CSS features.
We have a 'quick bail check', in the PostCSS plugin to quickly bail if
we _konw_ that we don't need to handle any Tailwind CSS features. This
is useful in Next.js applications where every single CSS file will be
passed to the PostCSS plugin.
If you use custom font ins Next.js, each of those fonts will have a CSS
file as well.
Before we introduced `@reference`, we used `@import "tailwindcss"
reference`, which passed the bail check because `@import` was being
used. Now we have `@reference` which wasn't included in the list.
This is now solved.
Fixes: #16056
### Test plan
Added a failing test that is now failing after the fix.
---
CHANGELOG.md | 1 +
.../@tailwindcss-postcss/src/index.test.ts | 50 +++++++++++++++++++
packages/@tailwindcss-postcss/src/index.ts | 2 +
3 files changed, 53 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e3481c52a7ba..c13589a9d1e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020))
+- Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057))
## [4.0.1] - 2025-01-29
diff --git a/packages/@tailwindcss-postcss/src/index.test.ts b/packages/@tailwindcss-postcss/src/index.test.ts
index 97eedb95d183..97eee3c1c5f0 100644
--- a/packages/@tailwindcss-postcss/src/index.test.ts
+++ b/packages/@tailwindcss-postcss/src/index.test.ts
@@ -248,6 +248,56 @@ test('bail early when Tailwind is not used', async () => {
`)
})
+test('handle CSS when only using a `@reference` (we should not bail early)', async () => {
+ let processor = postcss([
+ tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
+ ])
+
+ let result = await processor.process(
+ css`
+ @reference "tailwindcss/theme.css";
+
+ .foo {
+ @variant md {
+ bar: baz;
+ }
+ }
+ `,
+ { from: inputCssFilePath() },
+ )
+
+ expect(result.css.trim()).toMatchInlineSnapshot(`
+ "@media (width >= 48rem) {
+ .foo {
+ bar: baz;
+ }
+ }"
+ `)
+})
+
+test('handle CSS when using a `@variant` using variants that do not rely on the `@theme`', async () => {
+ let processor = postcss([
+ tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
+ ])
+
+ let result = await processor.process(
+ css`
+ .foo {
+ @variant data-is-hoverable {
+ bar: baz;
+ }
+ }
+ `,
+ { from: inputCssFilePath() },
+ )
+
+ expect(result.css.trim()).toMatchInlineSnapshot(`
+ ".foo[data-is-hoverable] {
+ bar: baz;
+ }"
+ `)
+})
+
test('runs `Once` plugins in the right order', async () => {
let before = ''
let after = ''
diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts
index 084f3671586f..d027fbd9ca67 100644
--- a/packages/@tailwindcss-postcss/src/index.ts
+++ b/packages/@tailwindcss-postcss/src/index.ts
@@ -77,7 +77,9 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
root.walkAtRules((node) => {
if (
node.name === 'import' ||
+ node.name === 'reference' ||
node.name === 'theme' ||
+ node.name === 'variant' ||
node.name === 'config' ||
node.name === 'plugin' ||
node.name === 'apply'
From 224294122b9743410dcb24d94ecf5d1468dde066 Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Thu, 30 Jan 2025 16:46:29 +0100
Subject: [PATCH 03/15] Refactor gradient implementation to work around
prettier/prettier#17058 (#16072)
This PR fixes an issue where tools like Prettier remove important
trailing commas in CSS variables, making gradients invalid.
We encoded the `,` in the `--tw-gradient-position` to ensure that _if_
the `var(--tw-gradient-position)` is used, that the `,` was there. And
if the variable was _not_ used that we didn't end up with a double `,,`
rendering the gradient invalid.
However, when running Prettier (there might be other tools that do this
as well), the trailing comma in the `--tw-gradient-position` was removed
which made the entire gradient invalid. E.g.:
```diff
.bg-gradient-to-r {
- --tw-gradient-position: to right in oklab,;
+ --tw-gradient-position: to right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
```
Notice how the `,` is removed.
This PR fixes that, by moving the `,` to where the variable is being
used. The only side effect is that we have to guarantee that the
`--tw-gradient-position` is always present. In our testing (and using UI
tests) this should be the case.
Related Prettier issue:
https://github.com/prettier/prettier/issues/17058
Fixes: #16037
---
CHANGELOG.md | 1 +
.../src/compat/legacy-utilities.test.ts | 16 +-
.../src/compat/legacy-utilities.ts | 2 +-
packages/tailwindcss/src/utilities.test.ts | 174 +++++++++---------
packages/tailwindcss/src/utilities.ts | 22 +--
packages/tailwindcss/tests/ui.spec.ts | 4 +
6 files changed, 112 insertions(+), 107 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c13589a9d1e4..5ae4dd48f276 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020))
- Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057))
+- Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072))
## [4.0.1] - 2025-01-29
diff --git a/packages/tailwindcss/src/compat/legacy-utilities.test.ts b/packages/tailwindcss/src/compat/legacy-utilities.test.ts
index 4c5dd99d0816..9a828d673b78 100644
--- a/packages/tailwindcss/src/compat/legacy-utilities.test.ts
+++ b/packages/tailwindcss/src/compat/legacy-utilities.test.ts
@@ -22,42 +22,42 @@ test('bg-gradient-*', async () => {
),
).toMatchInlineSnapshot(`
".bg-gradient-to-b {
- --tw-gradient-position: to bottom in oklab, ;
+ --tw-gradient-position: to bottom in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-bl {
- --tw-gradient-position: to bottom left in oklab, ;
+ --tw-gradient-position: to bottom left in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-br {
- --tw-gradient-position: to bottom right in oklab, ;
+ --tw-gradient-position: to bottom right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-l {
- --tw-gradient-position: to left in oklab, ;
+ --tw-gradient-position: to left in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-r {
- --tw-gradient-position: to right in oklab, ;
+ --tw-gradient-position: to right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-t {
- --tw-gradient-position: to top in oklab, ;
+ --tw-gradient-position: to top in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-tl {
- --tw-gradient-position: to top left in oklab, ;
+ --tw-gradient-position: to top left in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-tr {
- --tw-gradient-position: to top right in oklab, ;
+ --tw-gradient-position: to top right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}"
`)
diff --git a/packages/tailwindcss/src/compat/legacy-utilities.ts b/packages/tailwindcss/src/compat/legacy-utilities.ts
index d86b1770ab6e..5711892d7359 100644
--- a/packages/tailwindcss/src/compat/legacy-utilities.ts
+++ b/packages/tailwindcss/src/compat/legacy-utilities.ts
@@ -14,7 +14,7 @@ export function registerLegacyUtilities(designSystem: DesignSystem) {
['tl', 'top left'],
]) {
designSystem.utilities.static(`bg-gradient-to-${value}`, () => [
- decl('--tw-gradient-position', `to ${direction} in oklab,`),
+ decl('--tw-gradient-position', `to ${direction} in oklab`),
decl('background-image', `linear-gradient(var(--tw-gradient-stops))`),
])
}
diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts
index d57bffeaabee..10720b167759 100644
--- a/packages/tailwindcss/src/utilities.test.ts
+++ b/packages/tailwindcss/src/utilities.test.ts
@@ -10160,257 +10160,257 @@ test('bg', async () => {
}
.-bg-conic-45\\/oklab {
- --tw-gradient-position: from calc(45 * -1) in oklab, ;
+ --tw-gradient-position: from calc(45 * -1) in oklab;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.-bg-linear-45, .-bg-linear-45\\/oklab {
- --tw-gradient-position: calc(45deg * -1) in oklab, ;
+ --tw-gradient-position: calc(45deg * -1) in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.-bg-linear-\\[1\\.3rad\\] {
- --tw-gradient-position: calc(74.4845deg * -1), ;
+ --tw-gradient-position: calc(74.4845deg * -1);
background-image: linear-gradient(var(--tw-gradient-stops, calc(74.4845deg * -1)));
}
.-bg-linear-\\[125deg\\] {
- --tw-gradient-position: calc(125deg * -1), ;
+ --tw-gradient-position: calc(125deg * -1);
background-image: linear-gradient(var(--tw-gradient-stops, calc(125deg * -1)));
}
.bg-conic-45\\/\\[in_hsl_longer_hue\\] {
- --tw-gradient-position: from 45deg in hsl longer hue, ;
+ --tw-gradient-position: from 45deg in hsl longer hue;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic-45\\/oklab {
- --tw-gradient-position: from 45deg in oklab, ;
+ --tw-gradient-position: from 45deg in oklab;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic-45\\/shorter {
- --tw-gradient-position: from 45deg in oklch shorter hue, ;
+ --tw-gradient-position: from 45deg in oklch shorter hue;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic\\/\\[in_hsl_longer_hue\\] {
- --tw-gradient-position: in hsl longer hue, ;
+ --tw-gradient-position: in hsl longer hue;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic\\/decreasing {
- --tw-gradient-position: in oklch decreasing hue, ;
+ --tw-gradient-position: in oklch decreasing hue;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic\\/hsl {
- --tw-gradient-position: in hsl, ;
+ --tw-gradient-position: in hsl;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic\\/increasing {
- --tw-gradient-position: in oklch increasing hue, ;
+ --tw-gradient-position: in oklch increasing hue;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic\\/longer {
- --tw-gradient-position: in oklch longer hue, ;
+ --tw-gradient-position: in oklch longer hue;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic\\/oklab {
- --tw-gradient-position: in oklab, ;
+ --tw-gradient-position: in oklab;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic\\/oklch {
- --tw-gradient-position: in oklch, ;
+ --tw-gradient-position: in oklch;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic\\/shorter {
- --tw-gradient-position: in oklch shorter hue, ;
+ --tw-gradient-position: in oklch shorter hue;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-conic\\/srgb {
- --tw-gradient-position: in srgb, ;
+ --tw-gradient-position: in srgb;
background-image: conic-gradient(var(--tw-gradient-stops));
}
.bg-linear-45 {
- --tw-gradient-position: 45deg in oklab, ;
+ --tw-gradient-position: 45deg in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-45\\/\\[in_hsl_longer_hue\\] {
- --tw-gradient-position: 45deg in hsl longer hue, ;
+ --tw-gradient-position: 45deg in hsl longer hue;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-45\\/oklab {
- --tw-gradient-position: 45deg in oklab, ;
+ --tw-gradient-position: 45deg in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-45\\/shorter {
- --tw-gradient-position: 45deg in oklch shorter hue, ;
+ --tw-gradient-position: 45deg in oklch shorter hue;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-\\[1\\.3rad\\] {
- --tw-gradient-position: 74.4845deg, ;
+ --tw-gradient-position: 74.4845deg;
background-image: linear-gradient(var(--tw-gradient-stops, 74.4845deg));
}
.bg-linear-\\[125deg\\] {
- --tw-gradient-position: 125deg, ;
+ --tw-gradient-position: 125deg;
background-image: linear-gradient(var(--tw-gradient-stops, 125deg));
}
.bg-linear-\\[to_bottom\\] {
- --tw-gradient-position: to bottom, ;
+ --tw-gradient-position: to bottom;
background-image: linear-gradient(var(--tw-gradient-stops, to bottom));
}
.bg-linear-to-b {
- --tw-gradient-position: to bottom in oklab, ;
+ --tw-gradient-position: to bottom in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-bl {
- --tw-gradient-position: to bottom left in oklab, ;
+ --tw-gradient-position: to bottom left in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-br {
- --tw-gradient-position: to bottom right in oklab, ;
+ --tw-gradient-position: to bottom right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-l {
- --tw-gradient-position: to left in oklab, ;
+ --tw-gradient-position: to left in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r {
- --tw-gradient-position: to right in oklab, ;
+ --tw-gradient-position: to right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/\\[in_hsl_longer_hue\\] {
- --tw-gradient-position: to right in hsl longer hue, ;
+ --tw-gradient-position: to right in hsl longer hue;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/\\[longer\\] {
- --tw-gradient-position: to right longer, ;
+ --tw-gradient-position: to right longer;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/decreasing {
- --tw-gradient-position: to right in oklch decreasing hue, ;
+ --tw-gradient-position: to right in oklch decreasing hue;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/hsl {
- --tw-gradient-position: to right in hsl, ;
+ --tw-gradient-position: to right in hsl;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/increasing {
- --tw-gradient-position: to right in oklch increasing hue, ;
+ --tw-gradient-position: to right in oklch increasing hue;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/longer {
- --tw-gradient-position: to right in oklch longer hue, ;
+ --tw-gradient-position: to right in oklch longer hue;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/oklab {
- --tw-gradient-position: to right in oklab, ;
+ --tw-gradient-position: to right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/oklch {
- --tw-gradient-position: to right in oklch, ;
+ --tw-gradient-position: to right in oklch;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/shorter {
- --tw-gradient-position: to right in oklch shorter hue, ;
+ --tw-gradient-position: to right in oklch shorter hue;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r\\/srgb {
- --tw-gradient-position: to right in srgb, ;
+ --tw-gradient-position: to right in srgb;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-t {
- --tw-gradient-position: to top in oklab, ;
+ --tw-gradient-position: to top in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-tl {
- --tw-gradient-position: to top left in oklab, ;
+ --tw-gradient-position: to top left in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-tr {
- --tw-gradient-position: to top right in oklab, ;
+ --tw-gradient-position: to top right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-radial-\\[circle_at_center\\] {
- --tw-gradient-position: circle at center, ;
+ --tw-gradient-position: circle at center;
background-image: radial-gradient(var(--tw-gradient-stops, circle at center));
}
.bg-radial\\/\\[in_hsl_longer_hue\\] {
- --tw-gradient-position: in hsl longer hue, ;
+ --tw-gradient-position: in hsl longer hue;
background-image: radial-gradient(var(--tw-gradient-stops));
}
.bg-radial\\/decreasing {
- --tw-gradient-position: in oklch decreasing hue, ;
+ --tw-gradient-position: in oklch decreasing hue;
background-image: radial-gradient(var(--tw-gradient-stops));
}
.bg-radial\\/hsl {
- --tw-gradient-position: in hsl, ;
+ --tw-gradient-position: in hsl;
background-image: radial-gradient(var(--tw-gradient-stops));
}
.bg-radial\\/increasing {
- --tw-gradient-position: in oklch increasing hue, ;
+ --tw-gradient-position: in oklch increasing hue;
background-image: radial-gradient(var(--tw-gradient-stops));
}
.bg-radial\\/longer {
- --tw-gradient-position: in oklch longer hue, ;
+ --tw-gradient-position: in oklch longer hue;
background-image: radial-gradient(var(--tw-gradient-stops));
}
.bg-radial\\/oklab {
- --tw-gradient-position: in oklab, ;
+ --tw-gradient-position: in oklab;
background-image: radial-gradient(var(--tw-gradient-stops));
}
.bg-radial\\/oklch {
- --tw-gradient-position: in oklch, ;
+ --tw-gradient-position: in oklch;
background-image: radial-gradient(var(--tw-gradient-stops));
}
.bg-radial\\/shorter {
- --tw-gradient-position: in oklch shorter hue, ;
+ --tw-gradient-position: in oklch shorter hue;
background-image: radial-gradient(var(--tw-gradient-stops));
}
.bg-radial\\/srgb {
- --tw-gradient-position: in srgb, ;
+ --tw-gradient-position: in srgb;
background-image: radial-gradient(var(--tw-gradient-stops));
}
@@ -10710,62 +10710,62 @@ test('from', async () => {
.from-\\[\\#0088cc\\] {
--tw-gradient-from: #08c;
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-\\[\\#0088cc\\]\\/50, .from-\\[\\#0088cc\\]\\/\\[0\\.5\\], .from-\\[\\#0088cc\\]\\/\\[50\\%\\] {
--tw-gradient-from: oklab(59.9824% -.06725 -.12414 / .5);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-\\[color\\:var\\(--my-color\\)\\] {
--tw-gradient-from: var(--my-color);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-\\[color\\:var\\(--my-color\\)\\]\\/50, .from-\\[color\\:var\\(--my-color\\)\\]\\/\\[0\\.5\\], .from-\\[color\\:var\\(--my-color\\)\\]\\/\\[50\\%\\] {
--tw-gradient-from: color-mix(in oklab, var(--my-color) 50%, transparent);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-\\[var\\(--my-color\\)\\] {
--tw-gradient-from: var(--my-color);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-\\[var\\(--my-color\\)\\]\\/50, .from-\\[var\\(--my-color\\)\\]\\/\\[0\\.5\\], .from-\\[var\\(--my-color\\)\\]\\/\\[50\\%\\] {
--tw-gradient-from: color-mix(in oklab, var(--my-color) 50%, transparent);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-current {
--tw-gradient-from: currentColor;
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-current\\/50, .from-current\\/\\[0\\.5\\], .from-current\\/\\[50\\%\\] {
--tw-gradient-from: color-mix(in oklab, currentColor 50%, transparent);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-inherit {
--tw-gradient-from: inherit;
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-red-500 {
--tw-gradient-from: var(--color-red-500);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-red-500\\/50, .from-red-500\\/\\[0\\.5\\], .from-red-500\\/\\[50\\%\\] {
--tw-gradient-from: color-mix(in oklab, var(--color-red-500) 50%, transparent);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-transparent {
--tw-gradient-from: transparent;
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-0\\% {
@@ -10929,73 +10929,73 @@ test('via', async () => {
.via-\\[\\#0088cc\\] {
--tw-gradient-via: #08c;
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-\\[\\#0088cc\\]\\/50, .via-\\[\\#0088cc\\]\\/\\[0\\.5\\], .via-\\[\\#0088cc\\]\\/\\[50\\%\\] {
--tw-gradient-via: oklab(59.9824% -.06725 -.12414 / .5);
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-\\[color\\:var\\(--my-color\\)\\] {
--tw-gradient-via: var(--my-color);
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-\\[color\\:var\\(--my-color\\)\\]\\/50, .via-\\[color\\:var\\(--my-color\\)\\]\\/\\[0\\.5\\], .via-\\[color\\:var\\(--my-color\\)\\]\\/\\[50\\%\\] {
--tw-gradient-via: color-mix(in oklab, var(--my-color) 50%, transparent);
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-\\[var\\(--my-color\\)\\] {
--tw-gradient-via: var(--my-color);
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-\\[var\\(--my-color\\)\\]\\/50, .via-\\[var\\(--my-color\\)\\]\\/\\[0\\.5\\], .via-\\[var\\(--my-color\\)\\]\\/\\[50\\%\\] {
--tw-gradient-via: color-mix(in oklab, var(--my-color) 50%, transparent);
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-current {
--tw-gradient-via: currentColor;
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-current\\/50, .via-current\\/\\[0\\.5\\], .via-current\\/\\[50\\%\\] {
--tw-gradient-via: color-mix(in oklab, currentColor 50%, transparent);
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-inherit {
--tw-gradient-via: inherit;
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-red-500 {
--tw-gradient-via: var(--color-red-500);
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-red-500\\/50, .via-red-500\\/\\[0\\.5\\], .via-red-500\\/\\[50\\%\\] {
--tw-gradient-via: color-mix(in oklab, var(--color-red-500) 50%, transparent);
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
.via-transparent {
--tw-gradient-via: transparent;
- --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
@@ -11158,62 +11158,62 @@ test('to', async () => {
.to-\\[\\#0088cc\\] {
--tw-gradient-to: #08c;
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-\\[\\#0088cc\\]\\/50, .to-\\[\\#0088cc\\]\\/\\[0\\.5\\], .to-\\[\\#0088cc\\]\\/\\[50\\%\\] {
--tw-gradient-to: oklab(59.9824% -.06725 -.12414 / .5);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-\\[color\\:var\\(--my-color\\)\\] {
--tw-gradient-to: var(--my-color);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-\\[color\\:var\\(--my-color\\)\\]\\/50, .to-\\[color\\:var\\(--my-color\\)\\]\\/\\[0\\.5\\], .to-\\[color\\:var\\(--my-color\\)\\]\\/\\[50\\%\\] {
--tw-gradient-to: color-mix(in oklab, var(--my-color) 50%, transparent);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-\\[var\\(--my-color\\)\\] {
--tw-gradient-to: var(--my-color);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-\\[var\\(--my-color\\)\\]\\/50, .to-\\[var\\(--my-color\\)\\]\\/\\[0\\.5\\], .to-\\[var\\(--my-color\\)\\]\\/\\[50\\%\\] {
--tw-gradient-to: color-mix(in oklab, var(--my-color) 50%, transparent);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-current {
--tw-gradient-to: currentColor;
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-current\\/50, .to-current\\/\\[0\\.5\\], .to-current\\/\\[50\\%\\] {
--tw-gradient-to: color-mix(in oklab, currentColor 50%, transparent);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-inherit {
--tw-gradient-to: inherit;
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-red-500 {
--tw-gradient-to: var(--color-red-500);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-red-500\\/50, .to-red-500\\/\\[0\\.5\\], .to-red-500\\/\\[50\\%\\] {
--tw-gradient-to: color-mix(in oklab, var(--color-red-500) 50%, transparent);
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-transparent {
--tw-gradient-to: transparent;
- --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-0\\% {
diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts
index 57f39e021358..9f4a1039be95 100644
--- a/packages/tailwindcss/src/utilities.ts
+++ b/packages/tailwindcss/src/utilities.ts
@@ -2370,7 +2370,7 @@ export function createUtilities(theme: Theme) {
value = negative ? `calc(${value} * -1)` : `${value}`
return [
- decl('--tw-gradient-position', `${value},`),
+ decl('--tw-gradient-position', value),
decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`),
]
}
@@ -2378,7 +2378,7 @@ export function createUtilities(theme: Theme) {
if (negative) return
return [
- decl('--tw-gradient-position', `${value},`),
+ decl('--tw-gradient-position', value),
decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`),
]
}
@@ -2398,7 +2398,7 @@ export function createUtilities(theme: Theme) {
let interpolationMethod = resolveInterpolationModifier(candidate.modifier)
return [
- decl('--tw-gradient-position', `${value} ${interpolationMethod},`),
+ decl('--tw-gradient-position', `${value} ${interpolationMethod}`),
decl('background-image', `linear-gradient(var(--tw-gradient-stops))`),
]
}
@@ -2425,7 +2425,7 @@ export function createUtilities(theme: Theme) {
if (candidate.modifier) return
let value = candidate.value.value
return [
- decl('--tw-gradient-position', `${value},`),
+ decl('--tw-gradient-position', value),
decl('background-image', `conic-gradient(var(--tw-gradient-stops,${value}))`),
]
}
@@ -2434,7 +2434,7 @@ export function createUtilities(theme: Theme) {
if (!candidate.value) {
return [
- decl('--tw-gradient-position', `${interpolationMethod},`),
+ decl('--tw-gradient-position', interpolationMethod),
decl('background-image', `conic-gradient(var(--tw-gradient-stops))`),
]
}
@@ -2446,7 +2446,7 @@ export function createUtilities(theme: Theme) {
value = negative ? `calc(${value} * -1)` : `${value}deg`
return [
- decl('--tw-gradient-position', `from ${value} ${interpolationMethod},`),
+ decl('--tw-gradient-position', `from ${value} ${interpolationMethod}`),
decl('background-image', `conic-gradient(var(--tw-gradient-stops))`),
]
}
@@ -2471,7 +2471,7 @@ export function createUtilities(theme: Theme) {
if (!candidate.value) {
let interpolationMethod = resolveInterpolationModifier(candidate.modifier)
return [
- decl('--tw-gradient-position', `${interpolationMethod},`),
+ decl('--tw-gradient-position', interpolationMethod),
decl('background-image', `radial-gradient(var(--tw-gradient-stops))`),
]
}
@@ -2480,7 +2480,7 @@ export function createUtilities(theme: Theme) {
if (candidate.modifier) return
let value = candidate.value.value
return [
- decl('--tw-gradient-position', `${value},`),
+ decl('--tw-gradient-position', value),
decl('background-image', `radial-gradient(var(--tw-gradient-stops,${value}))`),
]
}
@@ -2655,7 +2655,7 @@ export function createUtilities(theme: Theme) {
decl('--tw-gradient-from', value),
decl(
'--tw-gradient-stops',
- 'var(--tw-gradient-via-stops, var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))',
+ 'var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))',
),
],
position: (value) => [gradientStopProperties(), decl('--tw-gradient-from-position', value)],
@@ -2668,7 +2668,7 @@ export function createUtilities(theme: Theme) {
decl('--tw-gradient-via', value),
decl(
'--tw-gradient-via-stops',
- 'var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position)',
+ 'var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position)',
),
decl('--tw-gradient-stops', 'var(--tw-gradient-via-stops)'),
],
@@ -2681,7 +2681,7 @@ export function createUtilities(theme: Theme) {
decl('--tw-gradient-to', value),
decl(
'--tw-gradient-stops',
- 'var(--tw-gradient-via-stops, var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))',
+ 'var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))',
),
],
position: (value) => [gradientStopProperties(), decl('--tw-gradient-to-position', value)],
diff --git a/packages/tailwindcss/tests/ui.spec.ts b/packages/tailwindcss/tests/ui.spec.ts
index 2595d9982b93..53d8a71c9a48 100644
--- a/packages/tailwindcss/tests/ui.spec.ts
+++ b/packages/tailwindcss/tests/ui.spec.ts
@@ -157,6 +157,10 @@ for (let [classes, expected] of [
'bg-radial-[at_0%_0%,var(--color-red),transparent]',
'radial-gradient(at 0% 0%, rgb(255, 0, 0), rgba(0, 0, 0, 0))',
],
+ [
+ 'bg-radial-[at_center] from-red to-green',
+ 'radial-gradient(rgb(255, 0, 0) 0%, rgb(0, 255, 0) 100%)',
+ ],
]) {
test(`radial gradient, "${classes}"`, async ({ page }) => {
let { getPropertyValue } = await render(
From c09bb5e2562293c14248ad8fbad5bdb5b9b22058 Mon Sep 17 00:00:00 2001
From: Philipp Spiess
Date: Thu, 30 Jan 2025 16:58:20 +0100
Subject: [PATCH 04/15] Fix Vite issues with SolidStart (#16052)
Fixes #16045
This PR fixes two Vite issues found with SolidStart:
- SolidStart seems to emit an empty HTML chunk (where the content is
literally just `/`) with _no pathname_. Since we use the path to
generate an `id` for HTML chunks, this would currently cause a crash.
This was reported in #16045
- While testing the fix for the above, we also found that hot reloading
was not working in SolidStart since `4.0.0-alpha.22`. After doing some
bisecting we found that this is happening as SolidStart has the same
module ID in different servers and we were invalidating the root when we
shouldn't. After trying to restructure this code so that it only cleans
up the root when it is _no longer part of any server_, we noticed some
other compatibility issues with Nuxt and SvelteKit. It seems that the
safest bet is to no longer update a root at all during rebuilds in the
SSR step. This makes `invalidateAllRoots` a function that only notifiers
the servers about a change which is conceptually also less confusing.
## Test plan
- Added an integration test for SolidStart dev mode
- Manually tested the dev mode across all Vite based templates in
https://github.com/philipp-spiess/tailwindcss-playgrounds: Astro, Nuxt,
Remix, Solid, SvelteKit, and Vue.
---------
Co-authored-by: Robin Malfait
---
CHANGELOG.md | 2 +
integrations/vite/solidstart.test.ts | 108 ++++++++++++++++++++++++
packages/@tailwindcss-vite/src/index.ts | 28 +++---
3 files changed, 120 insertions(+), 18 deletions(-)
create mode 100644 integrations/vite/solidstart.test.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ae4dd48f276..82bb2a70551c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020))
- Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057))
- Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072))
+- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
+- Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
## [4.0.1] - 2025-01-29
diff --git a/integrations/vite/solidstart.test.ts b/integrations/vite/solidstart.test.ts
new file mode 100644
index 000000000000..bbe9ccc8868d
--- /dev/null
+++ b/integrations/vite/solidstart.test.ts
@@ -0,0 +1,108 @@
+import { candidate, css, fetchStyles, js, json, retryAssertion, test, ts } from '../utils'
+
+const WORKSPACE = {
+ 'package.json': json`
+ {
+ "type": "module",
+ "dependencies": {
+ "@solidjs/start": "^1",
+ "solid-js": "^1",
+ "vinxi": "^0",
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ }
+ }
+ `,
+ 'jsconfig.json': json`
+ {
+ "compilerOptions": {
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js"
+ }
+ }
+ `,
+ 'app.config.js': ts`
+ import { defineConfig } from '@solidjs/start/config'
+ import tailwindcss from '@tailwindcss/vite'
+
+ export default defineConfig({
+ vite: {
+ plugins: [tailwindcss()],
+ },
+ })
+ `,
+ 'src/entry-server.jsx': js`
+ // @refresh reload
+ import { createHandler, StartServer } from '@solidjs/start/server'
+
+ export default createHandler(() => (
+ (
+
+ {assets}
+
+ {children}
+ {scripts}
+
+
+ )}
+ />
+ ))
+ `,
+ 'src/entry-client.jsx': js`
+ // @refresh reload
+ import { mount, StartClient } from '@solidjs/start/client'
+
+ mount(() => , document.getElementById('app'))
+ `,
+ 'src/app.jsx': js`
+ import './app.css'
+ export default function App() {
+ return Hello world!
+ }
+ `,
+ 'src/app.css': css`@import 'tailwindcss';`,
+}
+
+test(
+ 'dev mode',
+ {
+ fs: WORKSPACE,
+ },
+ async ({ fs, spawn, expect }) => {
+ let process = await spawn('pnpm vinxi dev', {
+ env: {
+ TEST: 'false', // VERY IMPORTANT OTHERWISE YOU WON'T GET OUTPUT
+ NODE_ENV: 'development',
+ },
+ })
+
+ let url = ''
+ await process.onStdout((m) => {
+ let match = /Local:\s*(http.*)\//.exec(m)
+ if (match) url = match[1]
+ return Boolean(url)
+ })
+
+ await retryAssertion(async () => {
+ let css = await fetchStyles(url)
+ expect(css).toContain(candidate`underline`)
+ })
+
+ await retryAssertion(async () => {
+ await fs.write(
+ 'src/app.jsx',
+ js`
+ import './app.css'
+ export default function App() {
+ return Hello world!
+ }
+ `,
+ )
+
+ let css = await fetchStyles(url)
+ expect(css).toContain(candidate`underline`)
+ expect(css).toContain(candidate`font-bold`)
+ })
+ },
+)
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index c8976c0276da..86d5d6142d5c 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -63,7 +63,7 @@ export default function tailwindcss(): Plugin[] {
)
})
- function scanFile(id: string, content: string, extension: string, isSSR: boolean) {
+ function scanFile(id: string, content: string, extension: string) {
for (let dependency of IGNORED_DEPENDENCIES) {
// We validated that Vite IDs always use posix style path separators, even on Windows.
// In dev build, Vite precompiles dependencies
@@ -83,26 +83,16 @@ export default function tailwindcss(): Plugin[] {
}
if (updated) {
- invalidateAllRoots(isSSR)
+ invalidateAllRoots()
}
}
- function invalidateAllRoots(isSSR: boolean) {
+ function invalidateAllRoots() {
for (let server of servers) {
let updates: Update[] = []
- for (let [id, root] of roots.entries()) {
+ for (let [id] of roots.entries()) {
let module = server.moduleGraph.getModuleById(id)
- if (!module) {
- // Note: Removing this during SSR is not safe and will produce
- // inconsistent results based on the timing of the removal and
- // the order / timing of transforms.
- if (!isSSR) {
- // It is safe to remove the item here since we're iterating on a copy
- // of the keys.
- roots.delete(id)
- }
- continue
- }
+ if (!module) continue
roots.get(id).requiresRebuild = false
server.moduleGraph.invalidateModule(module)
@@ -113,7 +103,6 @@ export default function tailwindcss(): Plugin[] {
timestamp: Date.now(),
})
}
-
if (updates.length > 0) {
server.hot.send({ type: 'update', updates })
}
@@ -210,12 +199,15 @@ export default function tailwindcss(): Plugin[] {
// Scan all non-CSS files for candidates
transformIndexHtml(html, { path }) {
- scanFile(path, html, 'html', isSSR)
+ // SolidStart emits HTML chunks with an undefined path and the html content of `\`.
+ if (!path) return
+
+ scanFile(path, html, 'html')
},
transform(src, id, options) {
let extension = getExtension(id)
if (isPotentialCssRootFile(id)) return
- scanFile(id, src, extension, options?.ssr ?? false)
+ scanFile(id, src, extension)
},
},
From 0589d7d38600ebe6d2bb70fc8b1c68d42f769080 Mon Sep 17 00:00:00 2001
From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com>
Date: Fri, 31 Jan 2025 11:22:45 +0100
Subject: [PATCH 05/15] =?UTF-8?q?Update=20@playwright/test=201.49.1=20?=
=?UTF-8?q?=E2=86=92=201.50.0=20(minor)=20(#16091)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 2 +-
pnpm-lock.yaml | 46 ++++++++++++++++++----------------------------
2 files changed, 19 insertions(+), 29 deletions(-)
diff --git a/package.json b/package.json
index 047f461c5200..6ca67e553fad 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
},
"license": "MIT",
"devDependencies": {
- "@playwright/test": "^1.49.1",
+ "@playwright/test": "^1.50.0",
"@types/node": "catalog:",
"postcss": "8.5.1",
"postcss-import": "^16.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3d70043b4ac5..da5335af5802 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -29,8 +29,8 @@ importers:
.:
devDependencies:
'@playwright/test':
- specifier: ^1.49.1
- version: 1.49.1
+ specifier: ^1.50.0
+ version: 1.50.0
'@types/node':
specifier: 'catalog:'
version: 20.14.13
@@ -406,7 +406,7 @@ importers:
version: 3.3.3
next:
specifier: 15.1.4
- version: 15.1.4(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ version: 15.1.4(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react:
specifier: ^19.0.0
version: 19.0.0
@@ -440,7 +440,7 @@ importers:
dependencies:
next:
specifier: 15.1.4
- version: 15.1.4(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ version: 15.1.4(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react:
specifier: ^19.0.0
version: 19.0.0
@@ -1612,8 +1612,8 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
- '@playwright/test@1.49.1':
- resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==}
+ '@playwright/test@1.50.0':
+ resolution: {integrity: sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw==}
engines: {node: '>=18'}
hasBin: true
@@ -3301,13 +3301,13 @@ packages:
pkg-types@1.3.0:
resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==}
- playwright-core@1.49.1:
- resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==}
+ playwright-core@1.50.0:
+ resolution: {integrity: sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==}
engines: {node: '>=18'}
hasBin: true
- playwright@1.49.1:
- resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==}
+ playwright@1.50.0:
+ resolution: {integrity: sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==}
engines: {node: '>=18'}
hasBin: true
@@ -3396,10 +3396,6 @@ packages:
resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
engines: {node: ^10 || ^12 || >=14}
- postcss@8.4.49:
- resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
- engines: {node: ^10 || ^12 || >=14}
-
postcss@8.5.1:
resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
engines: {node: ^10 || ^12 || >=14}
@@ -4792,9 +4788,9 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
- '@playwright/test@1.49.1':
+ '@playwright/test@1.50.0':
dependencies:
- playwright: 1.49.1
+ playwright: 1.50.0
'@rollup/rollup-android-arm-eabi@4.20.0':
optional: true
@@ -6632,7 +6628,7 @@ snapshots:
natural-compare@1.4.0: {}
- next@15.1.4(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ next@15.1.4(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@next/env': 15.1.4
'@swc/counter': 0.1.3
@@ -6652,7 +6648,7 @@ snapshots:
'@next/swc-linux-x64-musl': 15.1.4
'@next/swc-win32-arm64-msvc': 15.1.4
'@next/swc-win32-x64-msvc': 15.1.4
- '@playwright/test': 1.49.1
+ '@playwright/test': 1.50.0
sharp: 0.33.5
transitivePeerDependencies:
- '@babel/core'
@@ -6798,11 +6794,11 @@ snapshots:
mlly: 1.7.3
pathe: 1.1.2
- playwright-core@1.49.1: {}
+ playwright-core@1.50.0: {}
- playwright@1.49.1:
+ playwright@1.50.0:
dependencies:
- playwright-core: 1.49.1
+ playwright-core: 1.50.0
optionalDependencies:
fsevents: 2.3.2
@@ -6890,12 +6886,6 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
- postcss@8.4.49:
- dependencies:
- nanoid: 3.3.7
- picocolors: 1.1.1
- source-map-js: 1.2.1
-
postcss@8.5.1:
dependencies:
nanoid: 3.3.8
@@ -7532,7 +7522,7 @@ snapshots:
vite@6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.1(patch_hash=gkqcezdn4goium3e3s43dhy4by))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0):
dependencies:
esbuild: 0.24.0
- postcss: 8.4.49
+ postcss: 8.5.1
rollup: 4.27.4
optionalDependencies:
'@types/node': 20.14.13
From d85e9cfcab27f6e42a7e8a791979e072015790a3 Mon Sep 17 00:00:00 2001
From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com>
Date: Fri, 31 Jan 2025 11:01:13 +0000
Subject: [PATCH 06/15] =?UTF-8?q?Update=20turbo=202.3.3=20=E2=86=92=202.3.?=
=?UTF-8?q?4=20(patch)=20(#16084)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.
### What changed?
#### ✳️ turbo (2.3.3 → 2.3.4) ·
[Repo](https://github.com/turborepo/turbo)
Sorry, we couldn't find anything useful about this release.
---

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.
All Depfu comment commands
- @depfu rebase
- Rebases against your default branch and
redoes this update
- @depfu recreate
- Recreates this PR, overwriting any edits
that you've made to it
- @depfu merge
- Merges this PR once your tests are passing and
conflicts are resolved
- @depfu cancel merge
- Cancels automatic merging of this
PR
- @depfu close
- Closes this PR and deletes the branch
- @depfu reopen
- Restores the branch and reopens this PR (if
it's closed)
- @depfu pause
- Ignores all future updates for this dependency
and closes this PR
- @depfu pause [minor|major]
- Ignores all future minor/major
updates for this dependency and closes this PR
- @depfu resume
- Future versions of this dependency will
create PRs again (leaves this PR as is)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
---
package.json | 2 +-
pnpm-lock.yaml | 58 +++++++++++++++++++++++++-------------------------
2 files changed, 30 insertions(+), 30 deletions(-)
diff --git a/package.json b/package.json
index 6ca67e553fad..4266843fffef 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"prettier-plugin-embed": "^0.4.15",
"prettier-plugin-organize-imports": "^4.0.0",
"tsup": "^8.2.4",
- "turbo": "^2.3.3",
+ "turbo": "^2.3.4",
"typescript": "^5.5.4",
"vitest": "^2.0.5"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index da5335af5802..4f4852d1daa2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -53,8 +53,8 @@ importers:
specifier: ^8.2.4
version: 8.2.4(jiti@2.4.2)(postcss@8.5.1)(tsx@4.19.1)(typescript@5.5.4)(yaml@2.6.0)
turbo:
- specifier: ^2.3.3
- version: 2.3.3
+ specifier: ^2.3.4
+ version: 2.3.4
typescript:
specifier: ^5.5.4
version: 5.5.4
@@ -3791,38 +3791,38 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
- turbo-darwin-64@2.3.3:
- resolution: {integrity: sha512-bxX82xe6du/3rPmm4aCC5RdEilIN99VUld4HkFQuw+mvFg6darNBuQxyWSHZTtc25XgYjQrjsV05888w1grpaA==}
+ turbo-darwin-64@2.3.4:
+ resolution: {integrity: sha512-uOi/cUIGQI7uakZygH+cZQ5D4w+aMLlVCN2KTGot+cmefatps2ZmRRufuHrEM0Rl63opdKD8/JIu+54s25qkfg==}
cpu: [x64]
os: [darwin]
- turbo-darwin-arm64@2.3.3:
- resolution: {integrity: sha512-DYbQwa3NsAuWkCUYVzfOUBbSUBVQzH5HWUFy2Kgi3fGjIWVZOFk86ss+xsWu//rlEAfYwEmopigsPYSmW4X15A==}
+ turbo-darwin-arm64@2.3.4:
+ resolution: {integrity: sha512-IIM1Lq5R+EGMtM1YFGl4x8Xkr0MWb4HvyU8N4LNoQ1Be5aycrOE+VVfH+cDg/Q4csn+8bxCOxhRp5KmUflrVTQ==}
cpu: [arm64]
os: [darwin]
- turbo-linux-64@2.3.3:
- resolution: {integrity: sha512-eHj9OIB0dFaP6BxB88jSuaCLsOQSYWBgmhy2ErCu6D2GG6xW3b6e2UWHl/1Ho9FsTg4uVgo4DB9wGsKa5erjUA==}
+ turbo-linux-64@2.3.4:
+ resolution: {integrity: sha512-1aD2EfR7NfjFXNH3mYU5gybLJEFi2IGOoKwoPLchAFRQ6OEJQj201/oNo9CDL75IIrQo64/NpEgVyZtoPlfhfA==}
cpu: [x64]
os: [linux]
- turbo-linux-arm64@2.3.3:
- resolution: {integrity: sha512-NmDE/NjZoDj1UWBhMtOPmqFLEBKhzGS61KObfrDEbXvU3lekwHeoPvAMfcovzswzch+kN2DrtbNIlz+/rp8OCg==}
+ turbo-linux-arm64@2.3.4:
+ resolution: {integrity: sha512-MxTpdKwxCaA5IlybPxgGLu54fT2svdqTIxRd0TQmpRJIjM0s4kbM+7YiLk0mOh6dGqlWPUsxz/A0Mkn8Xr5o7Q==}
cpu: [arm64]
os: [linux]
- turbo-windows-64@2.3.3:
- resolution: {integrity: sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==}
+ turbo-windows-64@2.3.4:
+ resolution: {integrity: sha512-yyCrWqcRGu1AOOlrYzRnizEtdkqi+qKP0MW9dbk9OsMDXaOI5jlWtTY/AtWMkLw/czVJ7yS9Ex1vi9DB6YsFvw==}
cpu: [x64]
os: [win32]
- turbo-windows-arm64@2.3.3:
- resolution: {integrity: sha512-dW4ZK1r6XLPNYLIKjC4o87HxYidtRRcBeo/hZ9Wng2XM/MqqYkAyzJXJGgRMsc0MMEN9z4+ZIfnSNBrA0b08ag==}
+ turbo-windows-arm64@2.3.4:
+ resolution: {integrity: sha512-PggC3qH+njPfn1PDVwKrQvvQby8X09ufbqZ2Ha4uSu+5TvPorHHkAbZVHKYj2Y+tvVzxRzi4Sv6NdHXBS9Be5w==}
cpu: [arm64]
os: [win32]
- turbo@2.3.3:
- resolution: {integrity: sha512-DUHWQAcC8BTiUZDRzAYGvpSpGLiaOQPfYXlCieQbwUvmml/LRGIe3raKdrOPOoiX0DYlzxs2nH6BoWJoZrj8hA==}
+ turbo@2.3.4:
+ resolution: {integrity: sha512-1kiLO5C0Okh5ay1DbHsxkPsw9Sjsbjzm6cF85CpWjR0BIyBFNDbKqtUyqGADRS1dbbZoQanJZVj4MS5kk8J42Q==}
hasBin: true
type-check@0.4.0:
@@ -7378,32 +7378,32 @@ snapshots:
fsevents: 2.3.3
optional: true
- turbo-darwin-64@2.3.3:
+ turbo-darwin-64@2.3.4:
optional: true
- turbo-darwin-arm64@2.3.3:
+ turbo-darwin-arm64@2.3.4:
optional: true
- turbo-linux-64@2.3.3:
+ turbo-linux-64@2.3.4:
optional: true
- turbo-linux-arm64@2.3.3:
+ turbo-linux-arm64@2.3.4:
optional: true
- turbo-windows-64@2.3.3:
+ turbo-windows-64@2.3.4:
optional: true
- turbo-windows-arm64@2.3.3:
+ turbo-windows-arm64@2.3.4:
optional: true
- turbo@2.3.3:
+ turbo@2.3.4:
optionalDependencies:
- turbo-darwin-64: 2.3.3
- turbo-darwin-arm64: 2.3.3
- turbo-linux-64: 2.3.3
- turbo-linux-arm64: 2.3.3
- turbo-windows-64: 2.3.3
- turbo-windows-arm64: 2.3.3
+ turbo-darwin-64: 2.3.4
+ turbo-darwin-arm64: 2.3.4
+ turbo-linux-64: 2.3.4
+ turbo-linux-arm64: 2.3.4
+ turbo-windows-64: 2.3.4
+ turbo-windows-arm64: 2.3.4
type-check@0.4.0:
dependencies:
From 88c890615a529cc8f7f253d75e25bd19bcf7e306 Mon Sep 17 00:00:00 2001
From: Siriwat K
Date: Fri, 31 Jan 2025 18:44:30 +0700
Subject: [PATCH 07/15] Prevent modifying CSS variables in plugins (#16103)
closes #16100
---------
Co-authored-by: Robin Malfait
Co-authored-by: Philipp Spiess
---
CHANGELOG.md | 1 +
.../tailwindcss/src/compat/plugin-api.test.ts | 32 +++++++++++++++++++
packages/tailwindcss/src/compat/plugin-api.ts | 13 +++++---
3 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 82bb2a70551c..7a95136a0e91 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072))
- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
- Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
+- Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103))
## [4.0.1] - 2025-01-29
diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts
index 539c04a3c66c..4502118f458f 100644
--- a/packages/tailwindcss/src/compat/plugin-api.test.ts
+++ b/packages/tailwindcss/src/compat/plugin-api.test.ts
@@ -1534,6 +1534,38 @@ describe('addBase', () => {
"
`)
})
+
+ test('does not modify CSS variables', async () => {
+ let input = css`
+ @plugin "my-plugin";
+ `
+
+ let compiler = await compile(input, {
+ loadModule: async () => ({
+ module: plugin(function ({ addBase }) {
+ addBase({
+ ':root': {
+ '--PascalCase': '1',
+ '--camelCase': '1',
+ '--UPPERCASE': '1',
+ },
+ })
+ }),
+ base: '/root',
+ }),
+ })
+
+ expect(compiler.build([])).toMatchInlineSnapshot(`
+ "@layer base {
+ :root {
+ --PascalCase: 1;
+ --camelCase: 1;
+ --UPPERCASE: 1;
+ }
+ }
+ "
+ `)
+ })
})
describe('addVariant', () => {
diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts
index 13673280769b..8db4d68973fb 100644
--- a/packages/tailwindcss/src/compat/plugin-api.ts
+++ b/packages/tailwindcss/src/compat/plugin-api.ts
@@ -499,15 +499,18 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] {
for (let [name, value] of entries) {
if (typeof value !== 'object') {
- if (!name.startsWith('--') && value === '@slot') {
- ast.push(rule(name, [atRule('@slot')]))
- } else {
+ if (!name.startsWith('--')) {
+ if (value === '@slot') {
+ ast.push(rule(name, [atRule('@slot')]))
+ continue
+ }
+
// Convert camelCase to kebab-case:
// https://github.com/postcss/postcss-js/blob/b3db658b932b42f6ac14ca0b1d50f50c4569805b/parser.js#L30-L35
name = name.replace(/([A-Z])/g, '-$1').toLowerCase()
-
- ast.push(decl(name, String(value)))
}
+
+ ast.push(decl(name, String(value)))
} else if (Array.isArray(value)) {
for (let item of value) {
if (typeof item === 'string') {
From 3aa0e494bf9b913a8e6f461fafe74e5a5c50e5e9 Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Fri, 31 Jan 2025 15:13:17 +0100
Subject: [PATCH 08/15] Do not emit `@keyframes` in `@theme reference` (#16120)
This PR fixes na issue where `@keyframes` were emitted if they wre in a
`@theme
reference` and anothe `@theme {}` (that is not a reference) was present.
E.g.:
```css
@reference "tailwindcss";
@theme {
/* ... */
}
```
Produces:
```css
:root, :host {
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes ping {
75%, 100% {
transform: scale(2);
opacity: 0;
}
}
@keyframes pulse {
50% {
opacity: 0.5;
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(-25%);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
50% {
transform: none;
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
}
```
With this PR, the produced CSS looks like this instead:
```css
:root, :host {
}
```
Note: the empty `:root, :host` will be solved in a subsequent PR.
### Test plan
Added some unit tests, and a dedicated integration test.
---
CHANGELOG.md | 1 +
integrations/cli/index.test.ts | 62 +++++++++++++++++++++++
packages/tailwindcss/src/index.test.ts | 69 ++++++++++++++++++++++++++
packages/tailwindcss/src/index.ts | 6 +++
4 files changed, 138 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a95136a0e91..361db062e60b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
- Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
- Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103))
+- Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120))
## [4.0.1] - 2025-01-29
diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts
index 60fc03e421f6..2e6b9ad03549 100644
--- a/integrations/cli/index.test.ts
+++ b/integrations/cli/index.test.ts
@@ -1196,3 +1196,65 @@ test(
`)
},
)
+
+test(
+ '@theme reference should never emit values',
+ {
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "workspace:^",
+ "@tailwindcss/cli": "workspace:^"
+ }
+ }
+ `,
+ 'src/index.css': css`
+ @reference "tailwindcss";
+
+ .keep-me {
+ color: red;
+ }
+ `,
+ },
+ },
+ 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'))
+
+ expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
+ "
+ --- ./dist/out.css ---
+ .keep-me {
+ color: red;
+ }
+ "
+ `)
+
+ await fs.write(
+ './src/index.css',
+ css`
+ @reference "tailwindcss";
+
+ /* Not a reference! */
+ @theme {
+ --color-pink: pink;
+ }
+
+ .keep-me {
+ color: red;
+ }
+ `,
+ )
+ expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
+ "
+ --- ./dist/out.css ---
+ .keep-me {
+ color: red;
+ }
+ "
+ `)
+ },
+)
diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts
index 8fba63cfccef..9f7962916d90 100644
--- a/packages/tailwindcss/src/index.test.ts
+++ b/packages/tailwindcss/src/index.test.ts
@@ -1530,6 +1530,75 @@ describe('Parsing themes values from CSS', () => {
`)
})
+ test('`@keyframes` added in `@theme reference` should not be emitted', async () => {
+ return expect(
+ await compileCss(
+ css`
+ @theme reference {
+ --animate-foo: foo 1s infinite;
+
+ @keyframes foo {
+ 0%,
+ 100% {
+ color: red;
+ }
+ 50% {
+ color: blue;
+ }
+ }
+ }
+ @tailwind utilities;
+ `,
+ ['animate-foo'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ".animate-foo {
+ animation: var(--animate-foo);
+ }"
+ `)
+ })
+
+ test('`@keyframes` added in `@theme reference` should not be emitted, even if another `@theme` block exists', async () => {
+ return expect(
+ await compileCss(
+ css`
+ @theme reference {
+ --animate-foo: foo 1s infinite;
+
+ @keyframes foo {
+ 0%,
+ 100% {
+ color: red;
+ }
+ 50% {
+ color: blue;
+ }
+ }
+ }
+
+ @theme {
+ --color-pink: pink;
+ }
+
+ @tailwind utilities;
+ `,
+ ['bg-pink', 'animate-foo'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root, :host {
+ --color-pink: pink;
+ }
+
+ .animate-foo {
+ animation: var(--animate-foo);
+ }
+
+ .bg-pink {
+ background-color: var(--color-pink);
+ }"
+ `)
+ })
+
test('theme values added as reference that override existing theme value suppress the output of the original theme value as a variable', async () => {
expect(
await compileCss(
diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts
index df6e09f90649..5947e73b810c 100644
--- a/packages/tailwindcss/src/index.ts
+++ b/packages/tailwindcss/src/index.ts
@@ -454,6 +454,12 @@ async function parseCss(
// 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
From 60e61950b9bb62d9290f2a9df3330f2e36fc3b8e Mon Sep 17 00:00:00 2001
From: Philipp Spiess
Date: Fri, 31 Jan 2025 15:20:18 +0100
Subject: [PATCH 09/15] Ensure escaped theme variables are handled correctly
(#16064)
This PR ensures that escaped theme variables are properly handled. We do
this by moving the `escape`/`unescape` responsibility back into the main
tailwindcss entrypoint that reads and writes from the CSS and making
sure that _all internal state of the `Theme` class are unescaped
classes.
However, due to us accidentally shipping the part where a dot in the
theme variable would translate to an underscore in CSS already, this
logic is going to stay as-is for now.
Here's an example test that visualizes the new changes:
```ts
expect(
await compileCss(
css`
@theme {
--spacing-*: initial;
--spacing-1\.5: 2.5rem;
--spacing-foo\/bar: 3rem;
}
@tailwind utilities;
`,
['m-1.5', 'm-foo/bar'],
),
).toMatchInlineSnapshot(`
":root, :host {
--spacing-1\.5: 2.5rem;
--spacing-foo\\/bar: 3rem;
}
.m-1\\.5 {
margin: var(--spacing-1\.5);
}
.m-foo\\/bar {
margin: var(--spacing-foo\\/bar);
}"
`)
```
## Test plan
- Added a unit test
- Ensure this works end-to-end using the Vite playground:
---
CHANGELOG.md | 1 +
.../src/compat/apply-config-to-theme.ts | 3 +-
packages/tailwindcss/src/index.test.ts | 43 +++++++++++++++++++
packages/tailwindcss/src/index.ts | 5 ++-
packages/tailwindcss/src/theme.ts | 23 ++++++----
5 files changed, 62 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 361db062e60b..60a55a08131c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020))
+- Ensure escaped theme variables are handled correctly ([#16064](https://github.com/tailwindlabs/tailwindcss/pull/16064))
- Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057))
- Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072))
- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.ts
index 3f18711c7f16..c57e4308fcb7 100644
--- a/packages/tailwindcss/src/compat/apply-config-to-theme.ts
+++ b/packages/tailwindcss/src/compat/apply-config-to-theme.ts
@@ -1,6 +1,5 @@
import type { DesignSystem } from '../design-system'
import { ThemeOptions } from '../theme'
-import { escape } from '../utils/escape'
import type { ResolvedConfig } from './config/types'
function resolveThemeValue(value: unknown, subValue: string | null = null): string | null {
@@ -55,7 +54,7 @@ export function applyConfigToTheme(
if (!name) continue
designSystem.theme.add(
- `--${escape(name)}`,
+ `--${name}`,
'' + value,
ThemeOptions.INLINE | ThemeOptions.REFERENCE | ThemeOptions.DEFAULT,
)
diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts
index 9f7962916d90..ec4671e97268 100644
--- a/packages/tailwindcss/src/index.test.ts
+++ b/packages/tailwindcss/src/index.test.ts
@@ -152,6 +152,49 @@ describe('compiling CSS', () => {
`)
})
+ test('unescapes theme variables and handles dots as underscore', async () => {
+ expect(
+ await compileCss(
+ css`
+ @theme {
+ --spacing-*: initial;
+ --spacing-1\.5: 1.5px;
+ --spacing-2_5: 2.5px;
+ --spacing-3\.5: 3.5px;
+ --spacing-3_5: 3.5px;
+ --spacing-foo\/bar: 3rem;
+ }
+ @tailwind utilities;
+ `,
+ ['m-1.5', 'm-2.5', 'm-2_5', 'm-3.5', 'm-foo/bar'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root, :host {
+ --spacing-1\\.5: 1.5px;
+ --spacing-2_5: 2.5px;
+ --spacing-3\\.5: 3.5px;
+ --spacing-3_5: 3.5px;
+ --spacing-foo\\/bar: 3rem;
+ }
+
+ .m-1\\.5 {
+ margin: var(--spacing-1\\.5);
+ }
+
+ .m-2\\.5, .m-2_5 {
+ margin: var(--spacing-2_5);
+ }
+
+ .m-3\\.5 {
+ margin: var(--spacing-3\\.5);
+ }
+
+ .m-foo\\/bar {
+ margin: var(--spacing-foo\\/bar);
+ }"
+ `)
+ })
+
test('adds vendor prefixes', async () => {
expect(
await compileCss(
diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts
index 5947e73b810c..d594397ede6e 100644
--- a/packages/tailwindcss/src/index.ts
+++ b/packages/tailwindcss/src/index.ts
@@ -27,6 +27,7 @@ import * as CSS from './css-parser'
import { buildDesignSystem, type DesignSystem } from './design-system'
import { Theme, ThemeOptions } from './theme'
import { createCssUtility } from './utilities'
+import { escape, unescape } from './utils/escape'
import { segment } from './utils/segment'
import { compoundsForSelectors, IS_VALID_VARIANT_NAME } from './variants'
export type Config = UserConfig
@@ -467,7 +468,7 @@ async function parseCss(
if (child.kind === 'comment') return
if (child.kind === 'declaration' && child.property.startsWith('--')) {
- theme.add(child.property, child.value ?? '', themeOptions)
+ theme.add(unescape(child.property), child.value ?? '', themeOptions)
return
}
@@ -526,7 +527,7 @@ async function parseCss(
for (let [key, value] of theme.entries()) {
if (value.options & ThemeOptions.REFERENCE) continue
- nodes.push(decl(key, value.value))
+ nodes.push(decl(escape(key), value.value))
}
let keyframesRules = theme.getKeyframes()
diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts
index a2d3c205fa4a..0b0ca96c8d11 100644
--- a/packages/tailwindcss/src/theme.ts
+++ b/packages/tailwindcss/src/theme.ts
@@ -41,10 +41,6 @@ export class Theme {
) {}
add(key: string, value: string, options = ThemeOptions.NONE): void {
- if (key.endsWith('\\*')) {
- key = key.slice(0, -2) + '*'
- }
-
if (key.endsWith('-*')) {
if (value !== 'initial') {
throw new Error(`Invalid theme value \`${value}\` for namespace \`${key}\``)
@@ -149,11 +145,20 @@ export class Theme {
#resolveKey(candidateValue: string | null, themeKeys: ThemeKey[]): string | null {
for (let namespace of themeKeys) {
let themeKey =
- candidateValue !== null
- ? (escape(`${namespace}-${candidateValue.replaceAll('.', '_')}`) as ThemeKey)
- : namespace
+ candidateValue !== null ? (`${namespace}-${candidateValue}` as ThemeKey) : namespace
+
+ if (!this.values.has(themeKey)) {
+ // If the exact theme key is not found, we might be trying to resolve a key containing a dot
+ // that was registered with an underscore instead:
+ if (candidateValue !== null && candidateValue.includes('.')) {
+ themeKey = `${namespace}-${candidateValue.replaceAll('.', '_')}` as ThemeKey
+
+ if (!this.values.has(themeKey)) continue
+ } else {
+ continue
+ }
+ }
- if (!this.values.has(themeKey)) continue
if (isIgnoredThemeKey(themeKey, namespace)) continue
return themeKey
@@ -167,7 +172,7 @@ export class Theme {
return null
}
- return `var(${this.#prefixKey(themeKey)})`
+ return `var(${escape(this.#prefixKey(themeKey))})`
}
resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null {
From deb33a93abbd94fd40fd2471f47df2e075a2107c Mon Sep 17 00:00:00 2001
From: Philipp Spiess
Date: Fri, 31 Jan 2025 15:23:32 +0100
Subject: [PATCH 10/15] Vite: Don't rebase urls that appear to be aliases
(#16078)
Closes #16039
This PR changes our URL rebasing logic used with Vite so that it does
not rebase URLs that look like common alias paths (e.g. urls starting in
`~`, `@` or `#`, etc.). Unfortunately this is only an approximation and
you can configure an alias for a path that starts with a regular
alphabetical character (e.g. `foo` => `./my/foo`) so this isn't a
perfect fix, however in practice most aliases will be prefixed with a
symbol to make it clear that it's an alias anyways.
One alternative we have considered is to only rebase URLs that we know
are relative (so they need to start with a `.`). This, however, will
break common CSS use cases where urls are loaded like this:
```css
background: image-set(
url('image1.jpg') 1x,
url('image2.jpg') 2x
);
```
So making this change felt like we only trade one GitHub issue for
another one.
In a more ideal scenario we try to resolve the URL with the Vite
resolver (we have to run the resolver and can't rely on the `resolve`
setting alone due to packages like
[`vite-tsconfig-paths`](https://www.npmjs.com/package/vite-tsconfig-paths)),
however even then we can have relative paths being resolvable to
different files based on wether they were rebased or not (e.g. when an
image with the same filename exists in two different paths).
So ultimately we settled on extending the already existing blocklist
(which we have taken from the Vite implementation) for now.
## Test plan
- Added unit test and it was tested with the Vite playground.
---------
Co-authored-by: Robin Malfait
---
CHANGELOG.md | 1 +
packages/@tailwindcss-node/src/urls.test.ts | 26 +++++++++++++++++++++
packages/@tailwindcss-node/src/urls.ts | 7 ++++--
3 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60a55a08131c..77bdc10f48bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072))
- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
- Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
+- Vite: Don't rebase urls that appear to be aliases ([#16078](https://github.com/tailwindlabs/tailwindcss/pull/16078))
- Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103))
- Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120))
diff --git a/packages/@tailwindcss-node/src/urls.test.ts b/packages/@tailwindcss-node/src/urls.test.ts
index 3378e45edbdc..16ba352a7f66 100644
--- a/packages/@tailwindcss-node/src/urls.test.ts
+++ b/packages/@tailwindcss-node/src/urls.test.ts
@@ -24,6 +24,20 @@ test('URLs can be rewritten', async () => {
background: url('/image.jpg');
background: url("/image.jpg");
+ /* Potentially Vite-aliased URLs: ignored */
+ background: url(~/image.jpg);
+ background: url(~/foo/image.jpg);
+ background: url('~/image.jpg');
+ background: url("~/image.jpg");
+ background: url(#/image.jpg);
+ background: url(#/foo/image.jpg);
+ background: url('#/image.jpg');
+ background: url("#/image.jpg");
+ background: url(@/image.jpg);
+ background: url(@/foo/image.jpg);
+ background: url('@/image.jpg');
+ background: url("@/image.jpg");
+
/* External URL: ignored */
background: url(http://example.com/image.jpg);
background: url('http://example.com/image.jpg');
@@ -109,6 +123,18 @@ test('URLs can be rewritten', async () => {
background: url(/foo/image.jpg);
background: url('/image.jpg');
background: url("/image.jpg");
+ background: url(~/image.jpg);
+ background: url(~/foo/image.jpg);
+ background: url('~/image.jpg');
+ background: url("~/image.jpg");
+ background: url(#/image.jpg);
+ background: url(#/foo/image.jpg);
+ background: url('#/image.jpg');
+ background: url("#/image.jpg");
+ background: url(@/image.jpg);
+ background: url(@/foo/image.jpg);
+ background: url('@/image.jpg');
+ background: url("@/image.jpg");
background: url(http://example.com/image.jpg);
background: url('http://example.com/image.jpg');
background: url("http://example.com/image.jpg");
diff --git a/packages/@tailwindcss-node/src/urls.ts b/packages/@tailwindcss-node/src/urls.ts
index c4d56deb7e60..e35b9d280a06 100644
--- a/packages/@tailwindcss-node/src/urls.ts
+++ b/packages/@tailwindcss-node/src/urls.ts
@@ -149,9 +149,12 @@ async function doUrlReplace(
return `${funcName}(${wrap}${newUrl}${wrap})`
}
-function skipUrlReplacer(rawUrl: string) {
+function skipUrlReplacer(rawUrl: string, aliases?: string[]) {
return (
- isExternalUrl(rawUrl) || isDataUrl(rawUrl) || rawUrl[0] === '#' || functionCallRE.test(rawUrl)
+ isExternalUrl(rawUrl) ||
+ isDataUrl(rawUrl) ||
+ !rawUrl[0].match(/[\.a-zA-Z0-9_]/) ||
+ functionCallRE.test(rawUrl)
)
}
From 95722020fe91d3b28737487e17c0fcbc6ac8a4b6 Mon Sep 17 00:00:00 2001
From: Philipp Spiess
Date: Fri, 31 Jan 2025 15:26:45 +0100
Subject: [PATCH 11/15] Vite: Transform `
+