This PR improves some of the signature computation logic. Right now,
when you want to convert `[font-weight:400]` to `font-normal` it's not
going to work because the signatures don't like up:
```css
/* [font-weight:400] */
.x {
font-weight: 400;
}
/* font-normal */
.x {
--tw-font-weight: 400;
font-weight: 400;
}
```
So this PR essentially ignores `--tw-{property}` _if_ the `{property}`
exists with the exact same value.
The main reason we have this, is to make composition of utilities
easier.
As with any of these upgrades, they are the expected behavior in most
cases, but there could always be a case where you don't want this, but
that's why the upgrade tool requires you to review each change before
applying it. Intellisense will recommend the simplified class, but it's
up to you to decide if you want to apply it or not.
There is a known edge case for `leading-{num}`, because the property is
`line-height` and the CSS variable is `--tw-leading`. Right now we map
`--tw-leading` to `line-height` but we can also update the leading
classes to use `--tw-line-height` instead but that feels like a bigger
breaking change _if_ people rely on these internal CSS variables...
This PR is a follow up of #18815 and #18816, but this time let's migrate
the `supports` theme keys.
Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
content: ['./src/**/*.html'],
theme: {
extend: {
supports: {
// Automatically handled by bare values (using CSS variable as the value)
foo: 'foo: var(--foo)', // parentheses are optional
bar: '(bar: var(--bar))',
// Not automatically handled because names differ
baz: 'qux: var(--foo)',
// ^^^ ^^^ ← different names
// Custom
grid: 'display: grid',
},
},
},
}
```
Then we would generate the following Tailwind CSS v4 CSS:
```css
@custom-variant supports-baz {
@supports (qux: var(--foo)) {
@slot;
}
}
@custom-variant supports-grid {
@supports (display: grid) {
@slot;
}
}
```
Notice how we didn't generate a custom variant for `data-foo` or
`data-bar` because those are automatically handled by bare values.
I also went with the longer form of `@custom-variant`, we could use the
single selector approach, but that felt less clear to me.
```css
@custom-variant supports-baz (@supports (qux: var(--foo)));
@custom-variant supports-grid (@supports (display: grid));
```
---------
Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
This PR is similar to and a follow up of #18815, but this time to
migrate the `data` theme keys.
Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
content: ['./src/**/*.html'],
theme: {
extend: {
data: {
// Automatically handled by bare values
foo: 'foo',
// ^^^ ^^^ ← same names
// Not automatically handled by bare values
bar: 'baz',
// ^^^ ^^^ ← different names
// Completely custom
checked: 'ui~="checked"',
},
},
},
}
```
Then we would generate the following Tailwind CSS v4 CSS:
```css
@custom-variant data-bar (&[data-baz]);
@custom-variant data-checked (&[data-ui~="checked"]);
```
Notice how we didn't generate a custom variant for `data-foo` because
those are automatically handled by bare values.
This PR migrates `aria` theme keys when migrating from Tailwind CSS v3
to v4.
While working on improving some of the error messages to get more
insights into why migrating the JS file changed
(https://github.com/tailwindlabs/tailwindcss/pull/18808), I ran into an
issue where I couldn't think of a good comment to why `aria` theme keys
were not being migrated. (Internally we have `aria` "blocked").
So instead of figuring out a good error message..., I just went ahead
and added the migration for `aria` theme keys.
Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
content: ['./src/**/*.html'],
theme: {
extend: {
aria: {
// Built-in (not really, but visible because of intellisense)
busy: 'busy="true"',
// Automatically handled by bare values
foo: 'foo="true"',
// ^^^ ^^^ ← same names
// Not automatically handled by bare values because names differ
bar: 'baz="true"',
// ^^^ ^^^ ← different names
// Completely custom
asc: 'sort="ascending"',
desc: 'sort="descending"',
},
},
},
}
```
Then we would generate the following Tailwind CSS v4 CSS:
```css
@custom-variant aria-bar (&[aria-baz="true"]);
@custom-variant aria-asc (&[aria-sort="ascending"]);
@custom-variant aria-desc (&[aria-sort="descending"]);
```
Notice how we didn't generate a custom variant for `aria-busy` or
`aria-foo` because those are automatically handled by bare values.
We could also emit a comment near the CSS to warn about the fact that
`@custom-variant` will always be sorted _after_ any other built-in
variants.
This could result in slightly different behavior, or different order of
classes when using `prettier-plugin-tailwindcss`.
I don't know how important this is, because before this PR we would just
use `@config './tailwind.config.js';`.
Edit: when using the `@config` we override `aria` and extend it, which
means that it would be in the expected order 🤔
---------
Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
This PR makes the migrations for templates much faster. To make this
work, I also had to move things around a bit (so you might want to check
this PR commit by commit). I also solved an issue by restructuring the
code.
### Performance
For starters, we barely applied any caching when migrating candidates
from α to β. The problem with this is that in big projects the same
candidates will appear _everywhere_, so caching is going to be useful
here.
One of the reasons why we didn't do any caching is that some migrations
were checking if a migration is actually safe to do. To do this, we were
checking the `location` (the location of the candidate in the template).
Since this location is unique for each template, caching was not
possible.
So the first order of business was to hoist the `isSafeMigration` check
up as the very first thing we do in the migration.
If we do this first, then the only remaining code relies on the
`DesignSystem`, `UserConfig` and `rawCandidate`.
In a project, the `DesignSystem` and `UserConfig` will be the same
during the migration, only the `rawCandidate` will be different which
means that we can move all this logic in a good old `DefaultMap` and
cache the heck out of it.
Running the numbers on our Tailwind Plus repo, this results in:
```
Total seen candidates: 2 211 844
Total migrated candidates: 7 775
Cache hits: 1 575 700
```
That's a lot of work we _don't_ have to do. Looking at the timings, the
template migration step goes from ~45s to ~10s because of this.
Another big benefit of this is that this makes migrations _actually_
safe. Before we were checking if a migration was safe to do in specific
migrations. But other migrations were still printing the candidate which
could still result in an unsafe migration.
For example when migrating the `blur` and the `shadow` classes, the
`isSafeMigration` was used. But if the input was `!flex` then the safety
check wasn't even checked in this specific migration.
### Safe migrations
Also made some changes to the `isSafeMigration` logic itself. We used to
start by checking the location, but thinking about the problem again,
the actual big problem we were running into is classes that are short
like `blur`, and `shadow` because they could be used in other contexts
than a Tailwind CSS class.
Inverting this logic means that more specific Tailwind CSS classes will
very likely _not_ cause any issues at all.
For example:
- If you have variants: `hover:focus:flex`
- If you have arbitrary properties: `[color:red]`
- If you have arbitrary values: `bg-[red]`
- If you have a modifier: `bg-red-500/50`
- If you have a `-` in the name: `bg-red-500`
Even better if we can't parse a candidate at all, we can skip the
migrations all together.
This brings us to the issue in #17974, one of the issues was already
solved by just hoisting the `isSafeMigration`. But to make the issue was
completely solved I also made sure that in Vue attributes like
`:active="…"` are also considered unsafe (note: `:class` is allowed).
Last but not least, in case of the `!duration` that got replaced with
`duration!` was solved by verifying that the candidate actually produces
valid CSS. We can compute the signature for this class.
The reason this wasn't thrown away earlier is because we can correctly
parse `duration` but `duration` on its own doesn't exist,
`duration-<number>` does exist as a functional utility which is why it
parsed in the first place.
Fixes: #17974
## Test plan
1. Ran the tool on our Tailwind UI Templates repo to compare the new
output with the "old" behavior and there were no differences in output.
2. Ran the tool on our Tailwind Plus repo, and the template migration
step went from ~45s to ~10s.
3. Added additional tests to verify the issues in #17974 are fixed.
[ci-all] let's run this on all CI platforms...
Fixes#16156
## Summary
This PR adds a new 3 -> 4 template migration that changes the casing of
in both utility values and modifier values from camelCase to kebab-case
to match the updated CSS variable names.
## Test plan
- Added integration test, see the diff in the PR.
This PR introduces a vastly improved upgrade migrations system, to
migrate your codebase and modernize your utilities to make use of the
latest variants and utilities.
It all started when I saw this PR the other day:
https://github.com/tailwindlabs/tailwindcss/pull/17790
I was about to comment "Don't forget to add a migration". But I've been
thinking about a system where we can automate this process away. This PR
introduces this system.
This PR introduces upgrade migrations based on the internal Design
System, and it mainly updates arbitrary variants, arbitrary properties
and arbitrary values.
## The problem
Whenever we ship new utilities, or you make changes to your CSS file by
introducing new `@theme` values, or adding new `@utility` rules. It
could be that the rest of your codebase isn't aware of that, but you
could be using these values.
For example, it could be that you have a lot of arbitrary properties in
your codebase, they look something like this:
```html
<div class="[color-scheme:dark] [text-wrap:balance]"></div>
```
Whenever we introduce new features in Tailwind CSS, you probably don't
keep an eye on the release notes and update all of these arbitrary
properties to the newly introduced utilities.
But with this PR, we can run the upgrade tool:
```console
npx -y @tailwindcss/upgrade@latest
```
...and it will upgrade your project to use the new utilities:
```html
<div class="scheme-dark text-balance"></div>
```
It also works for arbitrary values, for example imagine you have classes
like this:
```html
<!-- Arbitrary property -->
<div class="[max-height:1lh]"></div>
<!-- Arbitrary value -->
<div class="max-h-[1lh]"></div>
```
Running the upgrade tool again:
```console
npx -y @tailwindcss/upgrade@latest
```
... gives you the following output:
```html
<!-- Arbitrary property -->
<div class="max-h-lh"></div>
<!-- Arbitrary value -->
<div class="max-h-lh"></div>
```
This is because of the original PR I mentioned, which introduced the
`max-h-lh` utilities.
A nice benefit is that this output only has 1 unique class instead of 2,
which also potentially reduces the size of your CSS file.
It could also be that you are using arbitrary values where you (or a
team member) didn't even know an alternative solution existed.
E.g.:
```html
<div class="w-[48rem]"></div>
```
After running the upgrade tool you will get this:
```html
<div class="w-3xl"></div>
```
We can go further though. Since the release of Tailwind CSS v4, we
introduced the concept of "bare values". Essentially allowing you to
type a number on utilities where it makes sense, and we produce a value
based on that number.
So an input like this:
```html
<div class="border-[123px]"></div>
```
Will be optimized to just:
```html
<div class="border-123"></div>
```
This can be very useful for complex utilities, for example, how many
times have you written something like this:
```html
<div class="grid-cols-[repeat(16,minmax(0,1fr))]"></div>
```
Because up until Tailwind CSS v4, we only generated 12 columns by
default. But since v4, we can generate any number of columns
automatically.
Running the migration tool will give you this:
```html
<div class="grid-cols-16"></div>
```
### User CSS
But, what if I told you that we can keep going...
In [Catalyst](https://tailwindcss.com/plus/ui-kit) we often use classes
that look like this for accessibility reasons:
```html
<div class="text-[CanvasText] bg-[Highlight]"></div>
```
What if you want to move the `CanvasText` and `Highlight` colors to your
CSS:
```css
@import "tailwincdss";
@theme {
--color-canvas: CanvasText;
--color-highlight: Highlight;
}
```
If you now run the upgrade tool again, this will be the result:
```html
<div class="text-canvas bg-highlight"></div>
```
We never shipped a `text-canvas` or `bg-highlight` utility, but the
upgrade tool uses your own CSS configuration to migrate your codebase.
This will keep your codebase clean, consistent and modern and you are in
control.
Let's look at one more example, what if you have this in a lot of
places:
```html
<div class="[scrollbar-gutter:stable]"></div>
```
And you don't want to wait for the Tailwind CSS team to ship a
`scrollbar-stable` (or similar) feature. You can add your own utility:
```css
@import "tailwincdss";
@utility scrollbar-stable {
scrollbar-gutter: stable;
}
```
```html
<div class="scrollbar-stable"></div>
```
## The solution — how it works
There are 2 big things happening here:
1. Instead of us (the Tailwind CSS team) hardcoding certain migrations,
we will make use of the internal `DesignSystem` which is the source of
truth for all this information. This is also what Tailwind CSS itself
uses to generate the CSS file.
The internal `DesignSystem` is essentially a list of all:
1. The internal utilities
2. The internal variants
3. The default theme we ship
4. The user CSS
1. With custom `@theme` values
2. With custom `@custom-variant` implementations
3. With custom `@utility` implementations
2. The upgrade tool now has a concept of `signatures`
The signatures part is the most interesting one, and it allows us to be
100% sure that we can migrate your codebase without breaking anything.
A signature is some unique identifier that represents a utility. But 2
utilities that do the exact same thing will have the same signature.
To make this work, we have to make sure that we normalize values. One
such value is the selector. I think a little visualization will help
here:
| UTILITY | GENERATED SIGNATURE |
| ---------------- | ----------------------- |
| `[display:flex]` | `.x { display: flex; }` |
| `flex` | `.x { display: flex; }` |
They have the exact same signature and therefore the upgrade tool can
safely migrate them to the same utility.
For this we will prefer the following order:
1. Static utilities — essentially no brackets. E.g.: `flex`,
`grid-cols-2`
2. Arbitrary values — e.g.: `max-h-[1lh]`, `border-[2px]`
3. Arbitrary properties — e.g.: `[color-scheme:dark]`, `[display:flex]`
We also have to canonicalize utilities to there minimal form.
Essentially making sure we increase the chance of finding a match.
```
[display:_flex_] → [display:flex] → flex
[display:_flex] → [display:flex] → flex
[display:flex_] → [display:flex] → flex
[display:flex] → [display:flex] → flex
```
If we don't do this, then the signatures will be slightly different, due
to the whitespace:
| UTILITY | GENERATED SIGNATURE |
| ------------------ | ------------------------- |
| `[display:_flex_]` | `.x { display: flex ; }` |
| `[display:_flex]` | `.x { display: flex; }` |
| `[display:flex_]` | `.x { display: flex ; }` |
| `[display:flex]` | `.x { display: flex; }` |
### Other small improvements
A few other improvements are for optimizing existing utilities:
1. Remove unnecessary data types. E.g.:
- `bg-[color:red]` -> `bg-[red]`
- `shadow-[shadow:inset_0_1px_--theme(--color-white/15%)]` ->
`shadow-[inset_0_1px_--theme(--color-white/15%)]`
This also makes use of these signatures and if dropping the data type
results in the same signature then we can safely drop it.
Additionally, if a more specific utility exists, we will prefer that
one. This reduced ambiguity and the need for data types.
- `bg-[position:123px]` → `bg-position-[123px]`
- `bg-[123px]` → `bg-position-[123px]`
- `bg-[size:123px]` → `bg-size-[123px]`
2. Optimizing modifiers. E.g.:
- `bg-red-500/[25%]` → `bg-red-500/25`
- `bg-red-500/[100%]` → `bg-red-500`
- `bg-red-500/100` → `bg-red-500`
3. Hoist `not` in arbitrary variants
- `[@media_not_(prefers-color-scheme:dark)]:flex` →
`not-[@media_(prefers-color-scheme:dark)]:flex` → `not-dark:flex` (in
case you are using the default `dark` mode implementation
4. Optimize raw values that could be converted to bare values. This uses
the `--spacing` variable to ensure it is safe.
- `w-[64rem]` → `w-256`
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Fixes#17622.
Adds a specific handling case in `themeableValues()` in
`packages/tailwindcss/src/compat/apply-config-to-theme.ts`. It seems
like this has unique handling in v3 for an array value, whereby the
second value is treated as a `line-height`.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR improves the compatibility with Tailwind CSS v4 with unsupported
browsers with the goal to greatly improve compatibility with Safari 15.
To make this work, this PR makes the following changes to all code
- Change `oklab(…)` default theme values to use a percentage in the
first place (so instead of `--color-red-500: oklch(0.637 0.237 25.331);`
we now define it as `--color-red-500: oklch(63.7% 0.237 25.331);` since
this syntax has much broader support on Safari).
- Polyfill `@property` with a `@supports` query targeting older versions
of Safari and Firefox *
- Create fallbacks for the `color-mix(…)` function that use _inlined
color values from your theme_ so that they can be computed a compile
time by `lightningcss`. These fallbacks will convert to srgb to increase
compatibility.
- Create fallbacks for the _relative color_ feature used in the new
shadow utilities and using `color-mix(…)` in case _relative color_ is
applied on `currentcolor` (due to limited browser support)
- Create fallbacks for gradient interpolation methods (e.g. to support
`bg-linear-to-r/oklab`)
- Polyfill `@media` queries range syntax.
## A simplified example
Given this example CSS input:
```css
@import 'tailwindcss';
@source inline('from-cyan-500/50 bg-linear-45');
```
Here's the updated output CSS including the newly added polyfills and
updated `oklab` values:
```css
.bg-linear-45 {
--tw-gradient-position: 45deg;
background-image: linear-gradient(var(--tw-gradient-stops));
}
@supports (background-image: linear-gradient(in lab, red, red)) {
.bg-linear-45 {
--tw-gradient-position: 45deg in oklab;
}
}
.from-cyan-500\\/50 {
--tw-gradient-from: oklab(71.5% -.11682 -.08247 / .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));
}
@supports (color: color-mix(in lab, red, red)) {
.from-cyan-500\\/50 {
--tw-gradient-from: color-mix(in oklab, var(--color-cyan-500) 50%, transparent);
}
}
:root, :host {
--color-cyan-500: oklch(71.5% .143 215.221);
}
@supports (((-webkit-hyphens: none)) and (not (margin-trim: 1lh))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
@layer base {
*, :before, :after, ::backdrop {
--tw-gradient-position: initial;
--tw-gradient-from: #0000;
--tw-gradient-via: #0000;
--tw-gradient-to: #0000;
--tw-gradient-stops: initial;
--tw-gradient-via-stops: initial;
--tw-gradient-from-position: 0%;
--tw-gradient-via-position: 50%;
--tw-gradient-to-position: 100%;
}
}
}
@property --tw-gradient-position {
syntax: "*";
inherits: false
}
@property --tw-gradient-from {
syntax: "<color>";
inherits: false;
initial-value: #0000;
}
@property --tw-gradient-via {
syntax: "<color>";
inherits: false;
initial-value: #0000;
}
@property --tw-gradient-to {
syntax: "<color>";
inherits: false;
initial-value: #0000;
}
@property --tw-gradient-stops {
syntax: "*";
inherits: false
}
@property --tw-gradient-via-stops {
syntax: "*";
inherits: false
}
@property --tw-gradient-from-position {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0%;
}
@property --tw-gradient-via-position {
syntax: "<length-percentage>";
inherits: false;
initial-value: 50%;
}
@property --tw-gradient-to-position {
syntax: "<length-percentage>";
inherits: false;
initial-value: 100%;
}
```
## \* A note on `@property` polyfills and CSS modules
On Next.js, CSS module files are required to be _pure_, meaning that all
selectors must either be scoped to a class or an ID. Fortunatnyl for us,
this does not apply to `@property` rules which we've been using before
to initialize CSS variables.
However, since we're now bringing back the `@property` polyfills, that
would cause unexpected rules to be exported from the CSS file as this:
```css
@reference "tailwindcss";
.skew {
@apply skew-7;
}
```
Would turn to the following file:
```css
.skew {
/* … */
}
@supports (/*…*/) {
@layer base {
*, :before, :after, ::backdrop {
--tw-gradient-position: initial;
}
}
}
@property /* … */
```
Notice that this adds a `*` selector which is not considered pure.
Unfortunately there is no way for us to silence this warning or work
around it, as the dependency causing this errors
([`postcss-modules-local-by-default`](https://github.com/css-modules/postcss-modules-local-by-default))
is bundled into Next.js. To work around crashes, these polyfills will
not apply to CSS modules processed by the PostCSS extension for now.
## Testing on tailwindcss.com
To see the changes in effect, take a look at this screencast that
compares tailwindcss.com on iOS 15.5 with a version that has the patches
of this PR applied:
https://github.com/user-attachments/assets/1279d6f5-3c63-4f30-839c-198a789f4292
## Test plan
- Tested on tailwindcss.com via a preview build:
https://tailwindcss-com-git-legacy-browsers-tailwindlabs.vercel.app/
- Updated tests
- Ensure we also test on Chrome 111, Safari 16.4, Firefox 128 to
make sure we have no regressions. Also tested on Safari 16.4, 15.5, 18.0
Closes#16171
This PR handles `darkMode` variant configs containing braces (so
creating sub-rules) the same way we handle it in the interop layer.
Since the interop layer runs inside the `addVariant` API that we do not
run here, I instead oped to copy the one liner.
## Test plan
Updated one of the migration tests to include a rule that wasn't working
before. Ensured the new output works via
https://play.tailwindcss.com/nR99uhKtv3
This PR bumps the Prettier dependencies, and also pins the version.
Noticed that a PR with a single empty commit started failing at the time
of writing this
(https://github.com/tailwindlabs/tailwindcss/pull/16306). This is
because prettier released a new minor version which results in slightly
different output.
Let's bump prettier and handle the differences, but also pin the version
to avoid this in the future.
This PR replaces `@variant` with `@custom-variant` for registering
custom variants via your CSS.
In addition, this PR introduces `@variant` that can be used in your CSS
to use a variant while writing custom CSS.
E.g.:
```css
.btn {
background: white;
@variant dark {
background: black;
}
}
```
Compiles to:
```css
.btn {
background: white;
}
@media (prefers-color-scheme: dark) {
.btn {
background: black;
}
}
```
For backwards compatibility, the `@variant` rules that don't have a body
and are
defined inline:
```css
@variant hocus (&:hover, &:focus);
```
And `@variant` rules that are defined with a body and a `@slot`:
```css
@variant hocus {
&:hover, &:focus {
@slot;
}
}
```
Will automatically be upgraded to `@custom-variant` internally, so no
breaking changes are introduced with this PR.
---
TODO:
- [x] ~~Decide whether we want to allow multiple variants and if so,
what syntax should be used. If not, nesting `@variant <variant> {}` will
be the way to go.~~ Only a single `@variant <variant>` can be used, if
you want to use multiple, nesting should be used:
```css
.foo {
@variant hover {
@variant focus {
color: red;
}
}
}
```
This PR improves the codemod tool to simplify 2 things:
1. Whenever you have a `theme(…)` call, we try to change it to a
`var(…)`, but if that doesn't work for some reason, we will make sure to
at least convert it to the more modern `--theme(…)`.
2. When converting `theme(spacing.2)`, we used to convert it to
`calc(var(--spacing)*2)`, but now we will convert it to `--spacing(2)`
instead.
This PR prevents the migration of utilities detected in function names,
e.g.: the use of `shadow` inside `filter: 'drop-shadow(…)'`.
## Test plan
Have content like this in a project you're migrating using the upgrade
tool:
```js
{
filter: 'drop-shadow(0 0 0.5rem #000)'
}
```
This was verified by adding unit tests to the specific codemods and
adding an integration test.
This PR improves the integration tests in two ways:
1. Make the integration tests more reliable and thus less flakey
2. Make the integration tests faster (by introducing concurrency)
Tried a lot of different things to make sure that these tests are fast
and stable.
---
The biggest issue we noticed is that some tests are flakey, these are
tests with long running dev-mode processes where watchers are being used
and/or dev servers are created.
To solve this, all the tests that spawn a process look at stdout/stderr
and wait for a message from the process to know whether we can start
making changes.
For example, in case of an Astro project, you get a `watching for file
changes` message. In case of Nuxt project you can wait for an `server
warmed up in` and in case of Next.js there is a `Ready in` message.
These depend on the tools being used, so this is hardcoded per test
instead of a magically automatic solution.
These messages allow us to wait until all the initial necessary work,
internal watchers and/or dev servers are setup before we start making
changes to the files and/or request CSS stylesheets before the server(s)
are ready.
---
Another improvement is how we setup the dev servers. Before, we used to
try and get a free port on the system and use a `--port` flag or a
`PORT` environment variable. Instead of doing this (which is slow), we
rely on the process itself to show a URL with a port. Basically all
tools will try to find a free port if the default port is in use. We can
then use the stdout/stderr messages to get the URL and the port to use.
To reduce the amount of potential conflicts in ports, we used to run
every test and every file sequentially to basically guarantee that ports
are free. With this new approach where we rely on the process, I noticed
that we don't really run into this issue again (I reran the tests
multiple times and they were always stable)
<img width="316" alt="image"
src="https://github.com/user-attachments/assets/b75ddab4-f919-4995-85d0-f212b603e5c2"
/>
Note: these tests run Linux, Windows and macOS in this branch just for
testing purposes. Once this is done, we will only run Linux tests on PRs
and run all 3 of them on the `next` branch.
We do make the tests concurrent by default now, which in theory means
that there could be conflicts (which in practice means that the process
has to do a few more tries to find a free port). To reduce these
conflicts, we split up the integration tests such that Vite, PostCSS,
CLI, … tests all run in a separate job in the GitHub actions workflow.
<img width="312" alt="image"
src="https://github.com/user-attachments/assets/fe9a58a1-98eb-4d9b-8845-a7c8a7af5766"
/>
Comparing this branch against the `next` branch, this is what CI looks
like right now:
| `next` | `feat/improve-integration-tests` |
| --- | --- |
| <img width="594" alt="image"
src="https://github.com/user-attachments/assets/540d21eb-ab03-42e8-9f6f-b3a071fc7635"
/> | <img width="672" alt="image"
src="https://github.com/user-attachments/assets/8ef2e891-08a1-464b-9954-4153174ebce7"
/> |
There also was a point in time where I introduced sequential tests such
that all spawned processes still run after each other, but so far I
didn't run into issues if we keep them concurrent so I dropped that
code.
Some small changes I made to make things more reliable:
1. When relying on stdout/stderr messages, we split lines on `\n` and we
strip all the ANSI escapes which allows us to not worry about special
ANSI characters when finding the URL or a specific message to wait for.
2. Once a test is done, we `child.kill()` the spawned process. If that
doesn't work, for whatever reason, we run a `child.kill('SIGKILL')` to
force kill the process. This could technically lead to some memory or
files not being cleaned up properly, but once CI is done, everything is
thrown away anyway.
3. As you can see in the screenshots, I used some nicer names for the
workflows.
| `next` | `feat/improve-integration-tests` |
| --- | --- |
| <img width="276" alt="image"
src="https://github.com/user-attachments/assets/e574bb53-e21b-4619-9cdb-515431b255b9"
/> | <img width="179" alt="image"
src="https://github.com/user-attachments/assets/8bc75119-fb91-4500-a1d0-bd09f74c93ad"
/> |
They also look a bit nicer in the PR overview as well:
<img width="929" alt="image"
src="https://github.com/user-attachments/assets/04fc71fc-74b0-4e7c-9047-2aada664efef"
/>
The very last commit just filters out Windows and macOS tests again for
PRs (but they are executed on the `next` branch.
---
### Nest steps
I think for now we are in a pretty good state, but there are some things
we can do to further improve everything (mainly make things faster) but
aren't necessary. I also ran into issue while trying it so there is more
work to do.
1. More splits — instead of having a Vite folder and PostCSS folder, we
can go a step further and have folders for Next.js, Astro, Nuxt, Remix,
…
2. Caching — right now we have to run the build step for every OS on
every "job". We can re-use the work here by introducing a setup job that
the other jobs rely on. @thecrypticace and I tried it already, but were
running into some Bun specific Standalone CLI issues when doing that.
3. Remote caching — we could re-enable remote caching such that the
`build` step can be full turbo (e.g.: after a PR is merged in `next` and
we run everything again)
Closes#15071
This PR reverts the changes in #15036 which add consistent base styles
for buttons and form controls to Preflight.
While this felt like a good idea (for the reasons explained in that PR),
practically this is just too disruptive of a change for people upgrading
from v3 to v4.
While updating some of our projects to v4 we found ourselves adding
classes to undo styles more often than we expected, and it also felt
inconsistent to have to use a different set of classes to style a link
or a button when we wanted them to look the same.
We also decided it feels a little strange that you could change the
border color of an element without ever specifying that it should have a
border, for example this just feels a little wrong:
```html
<button class="border-blue-500">
```
We also needed to set a default `color-scheme` value for any of this
stuff to work which breaks the ability to use the `color-scheme` meta
tag.
Since this change was a fairly major breaking change and we aren't
feeling much benefit from it, it doesn't feel worth making this change
for v4.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
While testing the latest alpha release across Tailwind v3 projects, we
noticed one regression in relation to the default color of `<button>`
elements. In v3, the reset would change the default to `inherit` but in
v4 we would _not include it in the reset snippet inserted by the upgrade
too_.
This PR changes the upgrade snippet to include it:
```diff
/*
In Tailwind CSS v4, basic styles are applied to form elements by default. To
maintain compatibility with v3, the following resets have been added:
*/
@layer base {
input,
textarea,
select,
button {
border: 0px solid;
border-radius: 0;
padding: 0;
+ color: inherit;
background-color: transparent;
}
}
```
This PR also ensures that there's a newline between the two code
snippets.
## Test Plan
### Before

### After
<img width="1354" alt="Screenshot 2024-11-21 at 15 42 58"
src="https://github.com/user-attachments/assets/9a4503fe-683f-4d08-abf2-7dd111ed5428">
This PR introduces consistent base styles for buttons and form controls
in Tailwind CSS v4.
## Motivation
In v3, form elements lack default styles, which can be
confusing—especially when certain elements, like a text input without a
placeholder or value, are rendered completely invisible on the page.
The goal of this change is to provide reasonable default styles for
buttons, inputs, selects, and textareas that are (mostly) consistent
across all browsers while remaining easy to customize with your own
styles.
This improvement should make Tailwind more accessible for developers new
to the framework and more convenient in scenarios where you need to
quickly create demos (e.g., using Tailwind Play).
## Light and dark mode support
These styles support both light and dark mode, achieved using the
`light-dark()` CSS function. While browser support for this function is
still somewhat limited, Lightning CSS transpiles it to a CSS
variable-based approach that works in older browsers.
For this approach to function correctly, a default `color-scheme` must
be set in your CSS (as explained in [the Lightning CSS
documentation](https://lightningcss.dev/transpilation.html#light-dark()-color-function)).
This PR addresses this requirement by setting the `color-scheme` to
`light` on the `html` element in Preflight.
<img width="1712" alt="image"
src="https://github.com/user-attachments/assets/dba56368-1427-47b3-9419-7c2f6313a944">
<img width="1709" alt="image"
src="https://github.com/user-attachments/assets/3d84fcd2-9606-4626-8e03-164a1dce9018">
## Breaking changes
While we don’t expect these changes to significantly impact v3 users
upgrading to v4, there may be minor differences for those relying on the
simpler v3 styles.
For example, Preflight now applies a `border-radius` to buttons and form
controls. If you weren’t explicitly setting the border radius to `0` in
your project, you’ll need to do so to restore the previous look.
Thankfully, reverting to the v3 styles is straightforward—just add the
following reset to your CSS:
```css
@layer base {
input,
textarea,
select,
button {
border: 0px solid;
border-radius: 0;
padding: 0;
background-color: transparent;
}
}
```
It’s worth noting that this reset doesn't touch the
`::file-selector-button` styles that were added in this PR. This is
because it's not possible to reliably "undo" these styles and restore
the original user-agent styles (which is what was used in v3), as these
are different in each browser. However, these new styles actually match
the defaults in most browsers pretty closely, so hopefully this just
won't be an issue.
## Codemod
This PR includes a codemod that automatically inserts the above
mentioned v3 reset to help avoid breaking changes during the upgrade.
The codemod will insert the following CSS:
```css
/*
In Tailwind CSS v4, basic styles are applied to form elements by default. To
maintain compatibility with v3, the following resets have been added:
*/
@layer base {
input,
textarea,
select,
button {
border: 0px solid;
border-radius: 0;
padding: 0;
background-color: transparent;
}
}
```
## Testing
These changes have been tested across a wide range of browsers,
including Chrome, Safari, Firefox, Edge, and Opera on macOS and Windows,
as well as Safari, Chrome, Firefox, and several lesser-known browsers on
iOS and Android.
However, some quirks still exist in certain mobile browsers, such as iOS
Safari, which adds too much bottom padding below date and time inputs:
<img width="1548" alt="Screenshot 2024-11-20 at 3 57 20 PM"
src="https://github.com/user-attachments/assets/507c7724-ac41-4634-a2b3-61ac4917ebce">
The only reliable way to address these issues is by applying
`appearance: none` to these form controls. However, this felt too
opinionated for Preflight, so we’ve opted to leave such adjustments to
user-land implementations.
---------
Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This implements backwards compatibility for colors that use the old
`<alpha-value>` feature from v3. We can do this by replacing
`<alpha-value>` with `1` because we use `color-mix` to actually apply
opacity modifiers in v4.
In some local testing we ran the `@tailwindcss/upgrade` command twice in
a row. It would be great to get some feedback that this is not working,
so this PR now checks if it can resolve the installed version of
`tailwindcss` and if it can, it requires it to be < 4 (you can bypass
this check with `--force`).
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR adds support for handling v3 [`container` customizations
](https://tailwindcss.com/docs/container#customizing). This is done by
adding a custom utility to extend the core `container` utility. A
concrete example can be taken from the added integration test.
### Input
```ts
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.html'],
theme: {
container: {
center: true,
padding: {
DEFAULT: '2rem',
'2xl': '4rem',
},
screens: {
md: '48rem', // Matches a default --breakpoint
xl: '1280px',
'2xl': '1536px',
},
},
},
}
```
### Output
```css
@import "tailwindcss";
@utility container {
margin-inline: auto;
padding-inline: 2rem;
@media (width >= theme(--breakpoint-sm)) {
max-width: none;
}
@media (width >= 48rem) {
max-width: 48rem;
}
@media (width >= 1280px) {
max-width: 1280px;
}
@media (width >= 1536px) {
max-width: 1536px;
padding-inline: 4rem;
}
}
````
## Test Plan
This PR adds extensive tests to the compat layer as part of unit tests.
Additionally it does at a test to the codemod setup that shows that the
right `@utility` code is generated. Furthermore I compared the
implementation against v3 on both the compat layer and the custom
`@utility`:
https://github.com/user-attachments/assets/44d6cbfb-4861-4225-9593-602b719f628f
This PR reverts a change we made for v4 that added borders to inputs by
default. It feels like we have to go further than this for this to
actually be useful to anyone, and since there were no borders in v3 it's
also a breaking change.
If we wanted to make form elements look more "normal" out of the box I
think we need to do something more like this:
https://play.tailwindcss.com/icCwFLVp4z?file=css
But it's a huge rabbit hole and there are so many stupid details to get
right that it feels like an insurmountable task, and if we can't go all
the way with it it's better to just maximize compatibility with v3.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Part of the current changes, we also want to make the `--line-height-*`
namespace closer to the utility name so we're renaming it to
`--leading-*`:
```diff
@theme {
- --line-height-none: 1;
- --line-height-tight: 1.25;
- --line-height-snug: 1.375;
- --line-height-normal: 1.5;
- --line-height-relaxed: 1.625;
- --line-height-loose: 2;
/* ... */
+ --leading-none: 1;
+ --leading-tight: 1.25;
+ --leading-snug: 1.375;
+ --leading-normal: 1.5;
+ --leading-relaxed: 1.625;
+ --leading-loose: 2;
/* ... */
}
```
Notice that we did not change the nested values used in the `--text`
type scale, e.g.:
```css
@theme {
/* Type scale */
--text-xs: 0.75rem;
--text-xs--line-height: 1rem;
}
```
These do not refer to the `leading` utility and instead refer to nested
properties so we're keeping those as-is.
## Test Plan
Added cases to the CSS `theme()` variable/JS plugin tests (interop
layer) and the integration tests (codemod layer).
Part of the current changes, we also want to make the
`--letter-spacing-*` namespace closer to the utility name so we're
renaming it to `--tracking-*`:
```diff
@theme {
- --letter-spacing-tighter: -0.05em;
- --letter-spacing-tight: -0.025em;
- --letter-spacing-normal: 0em;
- --letter-spacing-wide: 0.025em;
- --letter-spacing-wider: 0.05em;
- --letter-spacing-widest: 0.1em;
/* ... */
+ --tracking-tighter: -0.05em;
+ --tracking-tight: -0.025em;
+ --tracking-normal: 0em;
+ --tracking-wide: 0.025em;
+ --tracking-wider: 0.05em;
+ --tracking-widest: 0.1em;
/* ... */
}
```
## Test Plan
Added cases to the CSS `theme()` variable/JS plugin tests (interop
layer) and the integration tests (codemod layer).
This PR adds migrations for the recent changes to the `--spacing` scale
done in #12263.
There are a few steps that we do to ensure we have the best upgrade
experience:
- If you are overwriting the `spacing` theme with custom values, we now
check if the new values are multiplies of the default spacing scale.
When they are, we can safely remove the overwrite.
- If you are extending the `spacing` theme, we will unset the default
`--spacing` scale and only use the values you provided.
- Any `theme()` function calls are replaced with `calc(var(--spacing) *
multiplier)` unless the values are extending the default scale.
One caveat here is for `theme()` key which can not be replaced with
`var()` (e.g. in `@media` attribute positions). These will not be able
to be replaced with `calc()` either so the following needs to stay
unmigrated:
```css
@media (max-width: theme(spacing.96)) {
.foo {
color: red;
}
}
```
## Test plan
We are mainly testing two scenarios: The JS config _extends_ the
`spacing` namespace and the JS config _overwrites_ the `spacing`
namespace. For both cases we have added an integration test each to
ensure this works as expected. The test contains a mixture of keys (some
of it matching the default multiples, some don't, some have different
scales, and some use non-numeric identifiers). In addition to asserting
on the created CSS `@theme`, we also ensure that `theme()` calls are
properly replaced.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR updates all of the `--font-size-*` variables to `--text-*`
instead to closer match the utility names.
```diff
@theme {
- --font-size-xs: 0.75rem;
- --font-size-xs--line-height: 1rem;
- --font-size-sm: 0.875rem;
- --font-size-sm--line-height: 1.25rem;
- --font-size-base: 1rem;
- --font-size-base--line-height: 1.5rem;
- --font-size-lg: 1.125rem;
- --font-size-lg--line-height: 1.75rem;
- --font-size-xl: 1.25rem;
- --font-size-xl--line-height: 1.75rem;
/* ... */
+ --text-xs: 0.75rem;
+ --text-xs--line-height: 1rem;
+ --text-sm: 0.875rem;
+ --text-sm--line-height: 1.25rem;
+ --text-base: 1rem;
+ --text-base--line-height: 1.5rem;
+ --text-lg: 1.125rem;
+ --text-lg--line-height: 1.75rem;
+ --text-xl: 1.25rem;
+ --text-xl--line-height: 1.75rem;
/* ... */
}
```
This is part of a bigger set of changes where we're renaming other theme
variables as well with the same goals, since many existing theme
variables like `--shadow-*` and `--radius-*` are already not using the
explicit CSS property name.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR fixes an issue where globs in you `content` configuration escape
the current "root" of the project.
This can happen if you have a folder, and you need to look up in the
tree (e.g.: when looking at another package in a monorepo, or in case of
a Laravel project where you want to look at mail templates).
This applies a similar strategy we already implement on the Rust side.
1. Expand braces in the globs
2. Move static parts of the `pattern` to the `base` of the glob entry
object
---
Given a project setup like this:
```
.
├── admin
│ ├── my-tailwind.config.ts
│ └── src
│ ├── abc.jpg
│ ├── index.html
│ ├── index.js
│ └── styles
│ └── input.css
├── dashboard
│ ├── src
│ │ ├── index.html
│ │ ├── index.js
│ │ ├── input.css
│ │ └── pickaday.css
│ └── tailwind.config.ts
├── package-lock.json
├── package.json
├── postcss.config.js
└── unrelated
└── index.html
7 directories, 14 files
```
If you then have this config:
```ts
// admin/my-tailwind.config.ts
export default {
content: {
relative: true,
files: ['./src/**/*.html', '../dashboard/src/**/*.html'],
// ^^ this is the important part, which escapes
// the current root of the project.
},
theme: {
extend: {
colors: {
primary: 'red',
},
},
},
}
```
Then before this change, running the command looks like this:
<img width="1760" alt="image"
src="https://github.com/user-attachments/assets/60e2dfc7-3751-4432-80e3-8b4b8f1083d4">
After this change, running the command looks like this:
<img width="1452" alt="image"
src="https://github.com/user-attachments/assets/5c47182c-119c-4732-a253-2dace7086049">
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
During the migration process, a lot of changes to the CSS file happen.
Some parts are converted, some parts are deleted and some new CSS is
added.
To make sure we are generating a sensible and good looking CSS file, we
will sort the final CSS and pretty print it.
The order we came up with looks like this:
```css
/* Imports */
@import "tailwindcss";
@import "../other.css";
/* Configuration */
@config "../path/to/tailwindcss.config.js";
@plugin "my-plugin-1";
@plugin "my-plugin-2";
@source "./foo/**/*.ts";
@source "./bar/**/*.ts";
@variant foo {}
@variant bar {}
@theme {}
/* Border compatibility CSS */
@layer base {}
/* Utilities */
@utility foo {}
@utility bar {}
/* Rest of your own CSS if any */
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR renames the `--font-family-*` theme variables to `--font-*` to
more closely match the utility names and be a bit more terse in general.
```diff
@theme {
- --font-family-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
- --font-family-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
- --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
+ --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;
}
```
This is part of a bigger set of changes where we're renaming other theme
variables as well with the same goals, since many existing theme
variables like `--shadow-*` and `--radius-*` are already not using the
explicit CSS property name.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
When migrating a project from Tailwind CSS v3 to Tailwind CSS v4, then
we started the migration process in the following order:
1. Migrate the JS/TS config file
2. Migrate the source files (found via the `content` option)
3. Migrate the CSS files
However, if you have a setup where you have multiple CSS root files
(e.g.: `frontend` and `admin` are separated), then that typically means
that you have an `@config` directive in your CSS files. These point to
the Tailwind CSS config file.
This PR changes the migration order to do the following:
1. Build a tree of all the CSS files
2. For each `@config` directive, migrate the JS/TS config file
3. For each JS/TS config file, migrate the source files
If a CSS file does not contain any `@config` directives, then we start
by filling in the `@config` directive with the default Tailwind CSS
config file (if found, or the one passed in). If no default config file
or passed in config file can be found, then we will error out (just like
we do now)
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR improves where we inject the border compatibility CSS. Before
this change we injected it if it was necessary in one of these spots:
- Above the first `@layer base` to group it together with existing
`@layer base` at-rules.
- If not present, after the last `@import`, to make sure that we emit
valid CSS because `@import` should be at the top (with a few
exceptions).
However, if you are working with multiple CSS files, then it could be
that we injected the border compatibility CSS multiple times if those
files met one of the above conditions.
To solve this, we now inject the border compatibility CSS with the same
rules as above, but we also have another condition:
The border compatibility CSS is only injected if the file also has a
`@import "tailwindcss";` _or_ `@import "tailwindcss/preflight";` in the
current file.
---
Added integration tests to make sure that we are generating what we
expect in a real environment. Some of the integration tests also use the
old `@tailwind` directives to make sure that the order of migrations is
correct (first migrate to `@import` syntax, then inject the border
compatibility CSS).
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR improves the heuristics around the important codemod (e.g.
`!border` => `border!`) as we noticed a few more cases where we the
current heuristics was not enough.
Specifically, we made it not migrate the candidate in the following
conditions:
- When there's an immediate property access: `{ "foo": !border.something
+ ""}`
- When it's used as condition in the template language: `<div
v-if="something && !border"></div>` or `<div x-if="!border"></div>`
## Test plan
I added test cases to the unit tests and updated the integration test to
contain a more sophisticated example.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR fixes an issue where JS configuration theme properties with dots
or slashes in them would not migrate correctly. E.g.:
```ts
import { type Config } from 'tailwindcss'
module.exports = {
theme: {
width: {
1.5: '0.375rem',
'1/2': '50%',
}
}
}
```
This should convert to:
```css
@theme {
--width-1_5: 0.375rem;
--width-1\/2: 50%;
}
```
_Note: We will likely change the `--width-1_5` key to `--width-1\.5` in
a follow-up PR._
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR adds a codemod that ensures that the border styles from Tailwind CSS v3 work as expected once your project is migrated to Tailwind CSS v4.
In Tailwind CSS v3, the default border color is `colors.gray.200` and in Tailwind CSS v4 the default border color is `currentColor`.
Similarly in Tailwind CSS v3, DOM elements such as `input`, `select`, and `textarea` have a border width of `0px`, in Tailwind CSS v4, we don't change the border width of these elements and keep them as `1px`.
If your project happens to already use the same value for the default border color (`currentColor`) as we use in Tailwind CSS v4, then nothing happens. But this is very unlikely, so we will make sure that we honor your `borderColor.DEFAULT` value.
If you didn't change the default values in your `tailwind.config.js`, then we will inject compatibility CSS using the default Tailwind CSS v3 values to ensure the default color and width are applied correctly.
The important candidate migration is one of the most broad we have since it matches for any utility that are prefixed with an exclamation mark.
When running the codemodes on our example projects, we noticed that this was instead creating false-positives with candidates used in code positions, e.g:
```ts
export default {
shouldNotUse: !border.shouldUse,
}
```
To prevent false-positives, this PR adds a heuristics to detect wether or not a candidate is used in a non-code position. We do this by checking the character before and after the modifier and only allow quotes or spaces.
This can cause candidates to not migrate that are valid Tailwind CSS classes, e.g.:
```ts
let classNames = `!underline${isHovered ? ' font-bold' : ''}`
```
This, however, is not a big issue since v4 can parse the v3 important prefix too.
This PR updates all of our OKCLH colors to use `0` instead of `none` due
to weird behavior in Chrome where using `color-mix` with colors using
`none` produces unexpected results:
<img width="1110" alt="image"
src="https://github.com/user-attachments/assets/2272e494-500b-4f75-b5c1-d41c714f0339">
Both `none` and `0` behave as expected in Safari and Firefox so
suspecting this is a bug in Chrome rather than spec'd behavior.
Fixes#14740
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR changes the migration of `content` rules in the JS config to CSS codemods.
When a `content` rule is processed which matches files that are _also matched by the automatic content discovery in v4_, we do not need to emit CSS for that rule.
Take, for example this v3 configuration file:
```ts
import { type Config } from 'tailwindcss'
module.exports = {
content: [
'./src/**/*.{html,js}',
'./node_modules/my-external-lib/**/*.{html}'
],
} satisfies Config
```
Provided the base directories match up, the first rule will also be covered by the automatic content discovery in v4 and thus we only need to convert the second rule to CSS:
```css
@import "tailwindcss";
@source '../node_modules/my-external-lib/**/*.{html}';
```
This PR extends our JS configuration to CSS migration by also allowing `plugins` with options.
An example of such config would be:
```js
import { type Config } from 'tailwindcss'
import myPlugin from "./myPlugin";
export default {
plugins: [
myPlugin({
class: "tw",
}),
],
} satisfies Config;
```
If the option object contains only values allowed in our CSS API, we can convert this to CSS entirely:
```css
@plugin './myPlugin' {
class: 'tw';
}
```
With the changes in #14672, it now becomes trivial to actually resolve
the config (while still retaining the reset behavior). This means that
we can now convert JS configs that use _functions_, e.g.:
```ts
import { type Config } from 'tailwindcss'
export default {
theme: {
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
},
} satisfies Config
```
This becomes:
```css
@import 'tailwindcss';
@theme {
--color-gray-50: #fafafa;
--color-gray-100: #f5f5f5;
--color-gray-200: #e5e5e5;
--color-gray-300: #d4d4d4;
--color-gray-400: #a3a3a3;
--color-gray-500: #737373;
--color-gray-600: #525252;
--color-gray-700: #404040;
--color-gray-800: #262626;
--color-gray-900: #171717;
--color-gray-950: #0a0a0a;
}
```
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR builds on top of the new [JS config to CSS
migration](https://github.com/tailwindlabs/tailwindcss/pull/14651) and
extends it to support migrating _static_ plugins.
What are _static_ plugins you might ask? Static plugins are plugins
where we can statically determine that these are coming from a different
file (so there is nothing inside the JS config that creates them). An
example for this is this config file:
```js
import typographyPlugin from '@tailwindcss/typography'
import { type Config } from 'tailwindcss'
export default {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
darkMode: 'selector',
plugins: [typographyPlugin],
} satisfies Config
```
Here, the `plugins` array only has one element and it is a static import
from the `@tailwindcss/typography` module. In this PR we attempt to
parse the config file via Tree-sitter to extract the following
information from this file:
- What are the contents of the `plugins` array
- What are statically imported resources from the file
We then check if _all_ entries in the `plugins` array are either static
resources or _strings_ (something I saw working in some tests but I’m
not sure it still does). We migrate the JS config file to CSS if all
plugins are static and we can migrate them to CSS `@plugin` calls.
## Todo
This will need to be rebased after the updated tests in #14648
A few theme keys have changed in v4 relative to v3:
- `screens` -> `--breakpoint-*`
- `colors` -> `--color-*`
- `animation` -> `--animate-*`
- `borderRadius` -> `--radius-*`
- `boxShadow` -> `--shadow-*`
When using the `theme()` function we wouldn't pick up values from the
CSS for some of these. Likewise, when loading a v3 config not all of
these would be pushed back into the CSS theme and they should've been.
This PR addresses both of these problems.
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>