## Summary
- Fixes a typo: "a arbitrary" → "an arbitrary" in a comment/description.
## Details
- This is a documentation-only change. No code logic is affected.
## Test Plan
- N/A (doc-only)
Co-authored-by: 中野 博文 <hirofumi0082@gmail.com>
This PR fixes an issue where the `blur` in `wire:model.blur="…"` was
incorrectly migrated. We solved it by marking `wire:…` as an unsafe
region (`…` can be anything but whitespace).
Fixes: #18187
## Test plan
Added a test with this use case
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR adds some improvements to the upgrade tool where it can now also
migrate negative arbitrary values to negative bare values.
We already had support for the positive version of this:
```diff
- mb-[32rem]
+ mb-128
```
But now it can also handle negative values:
```diff
- mb-[-32rem]
+ -mb-128
```
The only tricky part here is that we had to hoist the `-` sign. Before
this PR, we were actually generating `mb--128` and that is invalid so it
was thrown out.
## Test plan
1. Added a test to ensure that the negative values are correctly
transformed.
This PR fixes 2 issues with the migration tool where certain classes
weren't migrated. This PR fixes those 2 scenarios:
### Scenario 1
When you have an arbitrary opacity modifier that doesn't use `%`, but is
just a number typically between `0` and `1` then this was not converted
to the bare value equivalent before.
E.g.:
```html
<div class="bg-[#f00]/[0.16]"></dv>
```
Will now be converted to:
```html
<div class="bg-[#f00]/16"></dv>
```
### Scenario 2
Fixes a bug when a CSS function was used in a fallback value in the CSS
variable shorthand syntax. In that case we didn't migrate the class to
the new syntax.
This was because we assumed that a `(` was found, that we are dealing
with a CSS function.
E.g.:
```html
<div class="w-[--spacing(1)]"></div>
^ This indicates a CSS function, we should not be
converting this to `w-(--spacing(1))`
```
But if a function was used as a fallback value, for example:
```html
<div class="bg-[--my-color,theme(colors.red.500)]"></dv>
```
Then we also didn't migrate it, but since the function call is in the
fallback, we can still migrate it.
Will now properly be converted to:
```html
<div class="bg-(--my-color,var(--color-red-500))"></dv>
```
## Test plan
1. Added a test for the first case
2. Added a test for the second case
3. Also added an integration-like test that runs all the migration steps
to make sure that the `theme(…)` in the fallback also gets updated to
`var(…)`. This one caught an issue because the `var(…)` wasn't handling
prefixes correctly.
I was testing to upgrade tool on various random projects just to see how
it behaves. Then I noticed an odd migration...
This PR fixes an issue where the upgrade tool accidentally migrated
classes such as `mt-[0px]` to `-mt-[0px]`. The reason for this is
because we are trying to find a replacement, and the computed signature
for both of them are exactly the same.
- `mt-[0px]` translates to:
```css
.x {
margin-top: 0px;
}
```
- `-mt-[0px]` translates to:
```css
.x {
margin-top: calc(0px * -1);
}
```
Which in turn translates to
```css
.x {
margin-top: 0px;
}
```
Notice that this is `0px`, not `-0px`.
Internally we use the roots of functional utilities to find
replacements. For intellisense purposes we typically show negative
versions before positive versions. This then means that we will try
`-mt-*` before `mt-*`. Because of the signature above, the `mt-[0px]`
was translated into `-mt-[0px]`.
We could solve this in a few ways. The first thing we can try is to make
sure that the signature is not the same and that `-mt-[0px]` actually
translates into `-0px` not `0px`.
This would solve our problem of the accidental migration. However, if we
_just_ sort the functional utilities roots such that the positive
versions exist before negative version and rely on the fact that
`-mt-[0px]` has the same signature. Then it also means that by doing
that we can migrate `-mt-[0px]` into `mt-[0px]` which is even better
because it's the same result and shorter.
## Test plan
1. Added a test to verify that `mt-[0px]` does not get migrated to
`-mt-[0px]`.
2. Added a test to verify that `-mt-[0px]` does get migrated to
`mt-[0px]`.
This PR adds an initial version for deprecated utilities. Right now it's
hardcoded to just the `order-none` utility.
This means that `order-0` and `order-[0]` will not be migrated to
`order-none` anymore. We did that automatically because we prefer named
utilities over bare values and arbitrary values.
With this PR, `order-none` is ignored.
Similarly, `order-none` will be migrated to `order-0` instead (defined
in a separate migration for deprecated values). Made it a new migration
instead of using the legacy migration because there all utilities still
exist, but are defined differently (e.g.: `shadow`, `shadow-sm`,
`shadow-xs`).
This PR is also an initial version, it doesn't add any form of
`deprecated` flag or feature on a per-utility implementation basis. This
therefor has the side effect that if you have a custom `order-none`
defined, that it will also be ignored during migrations.
## Test plan
1. Added tests to ensure the `order-0` is not migrated to `order-none`
2. Added tests to ensure `order-none` is migrated to `order-0` (if it's
safe to do so, the signature is still computed to ensure the output is
the same).
3. Ran this on the Tailwind Plus codebase and ensured that `order-0` is
not migrated to `order-none` and that `order-none` is migrated to
`order-0`.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR improves the performance of the upgrade tool due to a regression
introduced by https://github.com/tailwindlabs/tailwindcss/pull/18057
Essentially, we had to make sure that we are not in `<style>…</style>`
tags because we don't want to migrate declarations in there such as
`flex-shrink: 0;`
The issue with this approach is that we checked _before_ the candidate
if a `<style` cold be found and if we found an `</style>` tag after the
candidate.
We would basically do this check for every candidate that matches.
Running this on our Tailwind UI codebase, this resulted in a bit of a
slowdown:
```diff
- Before: ~13s
+ After: ~5m 39s
```
... quite the difference.
This is because we have a snapshot file that contains ~650k lines of
code. Looking for `<style>` and `</style>` tags in a file that large is
expensive, especially if we do it a lot.
I ran some numbers and that file contains ~1.8 million candidates.
Anyway, this PR fixes that by doing a few things:
1. We will compute the `<style>` and `</style>` tag positions only once
per file and cache it. This allows us to re-use this work for every
candidate that needs it.
2. We track the positions, which means that we can simply check if a
candidate's location is within any of 2 start and end tags. If so, we
skip it.
Running the numbers now gets us to:
```diff
- Before: ~5m 39s
+ After: ~9s
```
Much better!
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR fixes an issue where an error such as:
<img width="1702" alt="image"
src="https://github.com/user-attachments/assets/4e6f75c7-3182-4497-939e-96cff08c55ae"
/>
Will be thrown during the upgrade process. This can happen when you are
using `pnpm` and your CSS file includes a `@import "tailwindcss";`. In
this scenario, `tailwindcss` will be loaded from a shared `.pnpm` folder
outside of the current working directory.
In this case, we are also not interested in migrating _that_ file, but
we also don't want the upgrade process to just crash.
I didn't see an option to ignore errors like this, so wrapped it in a
try/catch instead.
It also fixes another issue where if you are using a pnpm workspace and
run the upgrade tool from the root, then it throws you an error that you
cannot add dependencies to the workspace root unless `-w` or
`--workspace-root` flags are passed.
For this, we disable the check entirely using the
`--ignore-workspace-root-check` flag. If we always used the
`--workspace-root` flag, then the dependencies would always be added to
the root, regardless of where you are running the script from which is
not what we want.
## Test plan
Before:
<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/78246876-3eb6-4539-a557-d3d366f1b3a3"
/>
After:
<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/a65e4421-d7c5-4d83-b35d-934708543e25"
/>
Before:
<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/53772661-2c4a-4212-84d9-a556a0ad320f"
/>
After:
<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/5bfaf20e-34b8-44fd-9b59-e72d36738879"
/>
This PR improves the upgrade tool by making sure that we don't migrate
CSS declarations in `<style>…</style>` blocks.
We do this by making sure that:
1. We detect a declaration, the current heuristic is that the candidate
is:
- Preceded by whitespace
- Followed by a colon and whitespace
```html
<style>
.foo {
flex-shrink: 0;
^ ^^
}
</style>
```
2. We are in a `<style>…</style>` block
```html
<style>
^^^^^^
.foo {
flex-shrink: 0;
}
</style>
^^^^^^^^
```
The reason we have these 2 checks is because just relying on the first
heuristic alone, also means that we will not be migrating keys in JS
objects, because they typically follow the same structure:
```js
let classes = {
flex: 0,
^ ^^
}
```
Another important thing to note is that we can't just ignore anything in
between `<style>…</style>` blocks, because you could still be using
`@apply` that we _do_ want to migrate.
Last but not least, the first heuristics is not perfect either. If you
are writing minified CSS then this will likely fail if there is no
whitespace around the candidate.
But my current assumption is that nobody should be writing minified CSS,
and minified CSS will very likely be generated and gitignored. In either
situation, replacements in minified CSS will not be any worse than it is
today.
I'm open to suggestions for better heuristics.
## Test plan
1. Added an integration test that verifies that we do migrate `@apply`
and don't migrate the `flex-shrink: 0;` declaration.
Fixes: #17975
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 improves the upgrade tool by also migrating bare values to named
values defined in the `@theme`.
Recently we shipped some updates dat allowed us to migrate arbitrary
values (with square brackets), but we didn't migrate bare values yet.
That means that in this example:
```html
<div class="aspect-[16/9]"></div>
<div class="aspect-16/9"></div>
```
We migrated this to:
```html
<div class="aspect-video"></div>
<div class="aspect-16/9"></div>
```
With this change, we will also try and migrate the bare value to a named
value. So this example:
```html
<div class="aspect-[16/9]"></div>
<div class="aspect-16/9"></div>
```
Now becomes:
```html
<div class="aspect-video"></div>
<div class="aspect-video"></div>
```
## Test plan
1. Added unit tests for the new functionality.
2. Ran this on a local project
Before:
<img width="432" alt="image"
src="https://github.com/user-attachments/assets/ce1adfbd-7be1-4062-bea5-66368f748e44"
/>
After:
<img width="382" alt="image"
src="https://github.com/user-attachments/assets/a385c94c-4e4c-4e1c-ac73-680c56ac4081"
/>
When passing `gitignore: true` to globby it will start a search for all
.gitignore files, this initial search includes node_modules making it
hang forever for large monorepos with many files inside node_modules
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>
This PR improves the upgrade tooling at tiny bit to make sure that as
long as we didn't change any of the stylesheets, that we also don't sort
internal nodes and/or format the stylesheet at all.
This is important in case the Prettier rules are different or if a
totally different formatter is used.
Essentially, if we didn't have to change the stylesheets because of a
migration, we don't want to change it due to a formatter either.
While working on another PR I noticed that some variants were re-printed
in an odd way.
Specifically, this PR fixes an issue where variants using the `@`-root
were incorrectly printed.
- `@lg` was printed as `@-lg`
- `@[400px]` was printed as `@-[400px]`
This is now special cased where the `-` is not inserted for `@`-root
variants.
## Test plan
1. Added a test to ensure the `@`-root variants are printed correctly.
This PR bumps all Tailwind CSS related dependencies when running the
upgrade tool _if_ the dependency exists in your package.json file.
E.g.: if you have `tailwindcss` and `@tailwindcss/vite` in your
package.json, then both will be updated to the latest version.
This PR is not trying to be smart and skip updating if you are already
on the latest version. It will just try and update the dependencies and
your package manager will do nothing in case it was already the latest
version.
## Test plan
Ran this on one of my personal projects and this was the output:
<img width="1023" alt="image"
src="https://github.com/user-attachments/assets/a189fe7a-a58a-44aa-9246-b720e7a2a892"
/>
Notice that I don't have `@tailwindcss/vite` logs because I am using
`@tailwindcss/postcss`.
This PR ensures that the `@tailwindcss/upgrade` tool works on existing
Tailwind CSS v4 projects. This PR also ensures that the upgrade tool is
idempotent, meaning that it can be run multiple times and it should
result in the same output.
One awesome feature this unlocks is that you can run the upgrade tool on
your codebase at any time and upgrade classes if you still have some
legacy syntaxes, such as `bg-[var(--my-color)]`, in your muscle memory.
One small note: If something changed in the first run, re-running will
not work immediately because your git repository will not be clean and
the upgrade tool requires your git repo to be clean. But once you
verified and committed your changes, the upgrade tool will be
idempotent.
Idempotency is guaranteed by ensuring that some migrations are skipped
by checking what version of Tailwind CSS you are on _before_ the version
is upgraded.
For the Tailwind CSS version: We will resolve `tailwindcss` itself to
know the _actual_ version that is installed (the one resolved from
`node_modules`). Not the one available in your package.json. Your
`package.json` could be out of sync if you reverted changes but didn't
run `npm install` yet.
Back to Idempotency:
For example, we have migrations where we change the variant order of
stacked variants. If we would run these migrations every time you run
the upgrade tool then we would be flip-flopping the order every run.
See: https://tailwindcss.com/docs/upgrade-guide#variant-stacking-order
Another example is where we rename some utilities. For example, we
rename:
| Before | After |
| ----------- | ----------- |
| `shadow` | `shadow-sm` |
| `shadow-sm` | `shadow-xs` |
Notice how we have `shadow-sm` in both the `before` and `after` column.
If we would run the upgrade tool again, then we would eventually migrate
your original `shadow` to `shadow-sm` (first run) and then to
`shadow-xs` (second run). Which would result in the wrong shadow.
See: https://tailwindcss.com/docs/upgrade-guide#renamed-utilities
---
The order of upgrade steps changed a bit as well to make the internals
are easier to work with and reason about.
1. Find CSS files
2. Link JS config files (if you are in a Tailwind CSS v3 project)
3. Migrate the JS config files (if you are in a Tailwind CSS v3 project)
4. Upgrade Tailwind CSS to v4 (or the latest version at that point)
5. Migrate the stylesheets (we used to migrate the source files first)
6. Migrate the source files
This is done so that step 5 and 6 will always operate on a Tailwind CSS
v4 project and we don't need to check the version number again. This is
also necessary because your CSS file will now very likely contain
`@import "tailwindcss";` which doesn't exist in Tailwind CSS v3.
This also means that we can rely on the same internals that Tailwind CSS
actually uses for locating the source files. We will use
`@tailwindcss/oxide`'s scanner to find the source files (and it also
keeps your custom `@source` directives into account).
This PR also introduces a few actual migrations related to recent
features and changes we shipped.
1. We migrate deprecated classes to their new names:
| Before | After |
| --------------------- | --------------------- |
| `bg-left-top` | `bg-top-left` |
| `bg-left-bottom` | `bg-bottom-left` |
| `bg-right-top` | `bg-top-right` |
| `bg-right-bottom` | `bg-bottom-right` |
| `object-left-top` | `object-top-left` |
| `object-left-bottom` | `object-bottom-left` |
| `object-right-top` | `object-top-right` |
| `object-right-bottom` | `object-bottom-right` |
Introduced in:
- https://github.com/tailwindlabs/tailwindcss/pull/17378
- https://github.com/tailwindlabs/tailwindcss/pull/17437
2. We migrate simple arbitrary variants to their dedicated variant:
| Before | After |
| ----------------------- | ------------------- |
| `[&:user-valid]:flex` | `user-valid:flex` |
| `[&:user-invalid]:flex` | `user-invalid:flex` |
Introduced in:
- https://github.com/tailwindlabs/tailwindcss/pull/12370
3. We migrate `@media` variants to their dedicated variant:
| Before | After |
| ----------------------------------------------------- |
------------------------- |
| `[@media_print]:flex` | `print:flex` |
| `[@media(prefers-reduced-motion:no-preference)]:flex` |
`motion-safe:flex` |
| `[@media(prefers-reduced-motion:reduce)]:flex` | `motion-reduce:flex`
|
| `[@media(prefers-contrast:more)]:flex` | `contrast-more:flex` |
| `[@media(prefers-contrast:less)]:flex` | `contrast-less:flex` |
| `[@media(orientation:portrait)]:flex` | `portrait:flex` |
| `[@media(orientation:landscape)]:flex` | `landscape:flex` |
| `[@media(forced-colors:active)]:flex` | `forced-colors:flex` |
| `[@media(inverted-colors:inverted)]:flex` | `inverted-colors:flex` |
| `[@media(pointer:none)]:flex` | `pointer-none:flex` |
| `[@media(pointer:coarse)]:flex` | `pointer-coarse:flex` |
| `[@media(pointer:fine)]:flex` | `pointer-fine:flex` |
| `[@media(any-pointer:none)]:flex` | `any-pointer-none:flex` |
| `[@media(any-pointer:coarse)]:flex` | `any-pointer-coarse:flex` |
| `[@media(any-pointer:fine)]:flex` | `any-pointer-fine:flex` |
| `[@media(scripting:none)]:flex` | `noscript:flex` |
The new variants related to `inverted-colors`, `pointer`, `any-pointer`
and `scripting` were introduced in:
- https://github.com/tailwindlabs/tailwindcss/pull/11693
- https://github.com/tailwindlabs/tailwindcss/pull/16946
- https://github.com/tailwindlabs/tailwindcss/pull/11929
- https://github.com/tailwindlabs/tailwindcss/pull/17431
This also applies to the `not` case, e.g.:
| Before | After |
| --------------------------------------------------------- |
----------------------------- |
| `[@media_not_print]:flex` | `not-print:flex` |
| `[@media_not(prefers-reduced-motion:no-preference)]:flex` |
`not-motion-safe:flex` |
| `[@media_not(prefers-reduced-motion:reduce)]:flex` |
`not-motion-reduce:flex` |
| `[@media_not(prefers-contrast:more)]:flex` | `not-contrast-more:flex`
|
| `[@media_not(prefers-contrast:less)]:flex` | `not-contrast-less:flex`
|
| `[@media_not(orientation:portrait)]:flex` | `not-portrait:flex` |
| `[@media_not(orientation:landscape)]:flex` | `not-landscape:flex` |
| `[@media_not(forced-colors:active)]:flex` | `not-forced-colors:flex` |
| `[@media_not(inverted-colors:inverted)]:flex` |
`not-inverted-colors:flex` |
| `[@media_not(pointer:none)]:flex` | `not-pointer-none:flex` |
| `[@media_not(pointer:coarse)]:flex` | `not-pointer-coarse:flex` |
| `[@media_not(pointer:fine)]:flex` | `not-pointer-fine:flex` |
| `[@media_not(any-pointer:none)]:flex` | `not-any-pointer-none:flex` |
| `[@media_not(any-pointer:coarse)]:flex` |
`not-any-pointer-coarse:flex` |
| `[@media_not(any-pointer:fine)]:flex` | `not-any-pointer-fine:flex` |
| `[@media_not(scripting:none)]:flex` | `not-noscript:flex` |
For each candidate, we run a set of upgrade migrations. If at the end of
the migrations the original candidate is still the same as the new
candidate, then we will parse & print the candidate one more time to
pretty print into consistent classes. Luckily parsing is cached so there
is no real downside overhead.
Consistency (especially with arbitrary variants and values) will reduce
your CSS file because there will be fewer "versions" of your class.
Concretely, the pretty printing will apply changes such as:
| Before | After |
| ---------------------- | ----------------- |
| `bg-[var(--my-color)]` | `bg-(--my-color)` |
| `bg-[rgb(0,_0,_0)]` | `bg-[rgb(0,0,0)]` |
Another big important reason for this change is that these classes on
their own
would have been migrated _if_ another migration was relevant for this
candidate.
This means that there are were some inconsistencies. E.g.:
| Before | After | Reason |
| ----------------------- | ---------------------- |
------------------------------------ |
| `!bg-[var(--my-color)]` | `bg-(--my-color)!` | Because the `!` is in
the wrong spot |
| `bg-[var(--my-color)]` | `bg-[var(--my-color)]` | Because no
migrations rand |
As you can see, the way the `--my-color` variable is used, is different.
This
changes will make sure it will now always be consistent:
| Before | After |
| ----------------------- | ---------------------- |
| `!bg-[var(--my-color)]` | `bg-(--my-color)!` |
| `bg-[var(--my-color)]` | `bg-(--my-color)` |
Yay!
Of course, if you don't want these more cosmetic changes, you can always
ignore the upgrade and revert these changes and only commit the changes
you want.
# Test plan
- All existing tests still pass.
- But I had to delete 1 test (we tested that Tailwind CSS v3 was
required).
- And had to mock the `version.isMajor` call to ensure we run the
individual migration tests correctly.
- Added new tests to test:
1. Migrating Tailwind CSS v4 projects works
1. Idempotency of the upgrade tool
[ci-all]
This PR is an internal refactor of the codemods package structure that
will make a few follow-up PRs easier.
Essentially what happens is:
1. Moved `./src/template/` into `src/codemods/template/`
2. Moved `./src/codemods` into `./src/codemods/css` (because the CSS
related codemods already)
3. Moved the migration files for the JS config, PostCSS config and
Prettier config into `./src/codemods/config/`.
4. Made filenames with actual migrations consistent by prefixing them
with `migrate-`.
5. Made sure that all the migration functions also use `migrate…`
When looking at this PR, go commit by commit, it will be easier. In a
lot of cases, it's just moving files around but those commits also come
with changes to the code just to update the imports.
[ci-all]
This PR adds a new source detection feature: `@source not "…"`. It can
be used to exclude files specifically from your source configuration
without having to think about creating a rule that matches all but the
requested file:
```css
@import "tailwindcss";
@source not "../src/my-tailwind-js-plugin.js";
```
While working on this feature, we noticed that there are multiple places
with different heuristics we used to scan the file system. These are:
- Auto source detection (so the default configuration or an `@source
"./my-dir"`)
- Custom sources ( e.g. `@source "./**/*.bin"` — these contain file
extensions)
- The code to detect updates on the file system
Because of the different heuristics, we were able to construct failing
cases (e.g. when you create a new file into `my-dir` that would be
thrown out by auto-source detection, it'd would actually be scanned). We
were also leaving a lot of performance on the table as the file system
is traversed multiple times for certain problems.
To resolve these issues, we're now unifying all of these systems into
one `ignore` crate walker setup. We also implemented features like
auto-source-detection and the `not` flag as additional _gitignore_ rules
only, avoid the need for a lot of custom code needed to make decisions.
High level, this is what happens after the now:
- We collect all non-negative `@source` rules into a list of _roots_
(that is the source directory for this rule) and optional _globs_ (that
is the actual rules for files in this file). For custom sources (i.e
with a custom `glob`), we add an allowlist rule to the gitignore setup,
so that we can be sure these files are always included.
- For every negative `@source` rule, we create respective ignore rules.
- Furthermore we have a custom filter that ensures files are only read
if they have been changed since the last time they were read.
So, consider the following setup:
```css
/* packages/web/src/index.css */
@import "tailwindcss";
@source "../../lib/ui/**/*.bin";
@source not "../../lib/ui/expensive.bin";
```
This creates a git ignore file that (simplified) looks like this:
```gitignore
# Auto-source rules
*.{exe,node,bin,…}
*.{css,scss,sass,…}
{node_modules,git}/
# Custom sources can overwrite auto-source rules
!lib/ui/**/*.bin
# Negative rules
lib/ui/expensive.bin
```
We then use this information _on top of your existing `.gitignore`
setup_ to resolve files (i.e so if your `.gitignore` contains rules e.g.
`dist/` this line is going to be added _before_ any of the rules lined
out in the example above. This allows negative rules to allow-list your
`.gitignore` rules.
To implement this, we're rely on the `ignore` crate but we had to make
various changes, very specific, to it so we decided to fork the crate.
All changes are prefixed with a `// CHANGED:` block but here are the
most-important ones:
- We added a way to add custom ignore rules that _extend_ (rather than
overwrite) your existing `.gitignore` rules
- We updated the order in which files are resolved and made it so that
more-specific files can allow-list more generic ignore rules.
- We resolved various issues related to adding more than one base path
to the traversal and ensured it works consistent for Linux, macOS, and
Windows.
## Behavioral changes
1. Any custom glob defined via `@source` now wins over your `.gitignore`
file and the auto-content rules.
- Resolves#16920
3. The `node_modules` and `.git` folders as well as the `.gitignore`
file are now ignored by default (but can be overridden by an explicit
`@source` rule).
- Resolves#17318
- Resolves#15882
4. Source paths into ignored-by-default folders (like `node_modules`)
now also win over your `.gitignore` configuration and auto-content
rules.
- Resolves#16669
5. Introduced `@source not "…"` to negate any previous rules.
- Resolves#17058
6. Negative `content` rules in your legacy JavaScript configuration
(e.g. `content: ['!./src']`) now work with v4.
- Resolves#15943
7. The order of `@source` definitions matter now, because you can
technically include or negate previous rules. This is similar to your
`.gitingore` file.
9. Rebuilds in watch mode now take the `@source` configuration into
account
- Resolves#15684
## Combining with other features
Note that the `not` flag is also already compatible with [`@source
inline(…)`](https://github.com/tailwindlabs/tailwindcss/pull/17147)
added in an earlier commit:
```css
@import "tailwindcss";
@source not inline("container");
```
## Test plan
- We added a bunch of oxide unit tests to ensure that the right files
are scanned
- We updated the existing integration tests with new `@source not "…"`
specific examples and updated the existing tests to match the subtle
behavior changes
- We also added a new special tag `[ci-all]` that, when added to the
description of a PR, causes the PR to run unit and integration tests on
all operating systems.
[ci-all]
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Closes#16945
This PR changes the `--theme(…)` function now return CSS `var(…)`
definitions unless used in places where `var(…)` is not valid CSS (e.g.
in `@media (width >= theme(--breakpoint-md))`):
```css
/* input */
@theme {
--color-red: red;
}
.red {
color: --theme(--color-red);
}
/* output */
:root, :host {
--color-red: red;
}
.red {
color: var(--color-red);
}
```
Furthermore, this adds an `--theme(… inline)` option to the `--theme(…)`
function to force the resolution to be inline, e.g.:
```css
/* input */
@theme {
--color-red: red;
}
.red {
color: --theme(--color-red inline);
}
/* output */
.red {
color: red;
}
```
This PR also changes preflight and the default theme to use this new
`--theme(…)` function to ensure variables are prefixed correctly.
## Test plan
- Added unit tests and a test that pulls in the whole preflight under a
prefix theme.
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
Closes#16391
Like the title suggest this PR adds error reporting when the `npm
install` or `npm remove` commands fail.
## Test plan
Tested by swapping out the command for `echo "bla"; exit 1` and
capturing the output from the integration tests:
<img width="792" alt="Screenshot 2025-02-13 at 14 33 02"
src="https://github.com/user-attachments/assets/d1288114-106a-4ac6-a54b-d02b74c98f35"
/>
<img width="761" alt="Screenshot 2025-02-13 at 14 31 05"
src="https://github.com/user-attachments/assets/6d5b9427-457f-4e67-9723-4e340da61749"
/>
Decided not to add a new test for this since it's unlikely we'll do big
changes here and the upgrade integration tests are already quite slow.
Resolves#16170
This PR fixes an issue where the previously opted-out escaping of the
first argument for the `var(…)` function was not unescaped at all. This
was introduced in https://github.com/tailwindlabs/tailwindcss/pull/14776
where the intention was to not require escaping of underscores in the
var function (e.g. `ml-[var(--spacing-1_5)]`). However, I do think it
still makes sense to unescape an eventually escaped underline for
consistency.
## Test plan
The example from #1670 now parses as expected:
<img width="904" alt="Screenshot 2025-02-03 at 13 51 35"
src="https://github.com/user-attachments/assets/cac0f06e-37da-4dcb-a554-9606d144a8d5"
/>
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR fixes the upgrade tool by properly migrating the
`leading-[<number>]` classes.
The issue is that `leading-[<number>]` maps to the number directly, but
if you use a bare value, then it's a multiplier for based on the
`--spacing` value.
E.g.:
*leading-[2]*:
```css
.leading-\[2\] {
--tw-leading: 2;
line-height: 2;
}
@property --tw-leading {
syntax: "*";
inherits: false;
}
```
*leading-2*:
```css
.leading-2 {
--tw-leading: calc(var(--spacing) * 2);
line-height: calc(var(--spacing) * 2);
}
@property --tw-leading {
syntax: "*";
inherits: false;
}
```
This PR will now prevent migrating arbitrary values to bare values for
`leading-*` utilities.
That said, this does introduce a small improvement where `leading-[1]`
is migrated to `leading-none`.
Fixes: https://github.com/tailwindlabs/tailwindcss/issues/15924
Closes#15220
This PR fixes an issue where the upgrade tool would not be able to load
some JavaScript config files across different drive letters on Windows.
The issue in detail is that `path.relative(…)` tries to build a relative
path but if the file is inside the same folder, it won't start the
relative path with a `./` so we manually appended it in case that it
isn't there. The issue on Windows specifically is that
`file.relative(…)` can also return a legit absolute path, e.g. when the
file is on a different drive. In this case we obviously don't want to
prefix a path with `./`.
## Test plan
To reproduce this issue, I checked out a Tailwind v3 project _on a
different drive letter than my Windows installation_. In that case, I
was adding a repo inside `D:` while `npm` was installed in `C:`. I then
run `npx @tailwindcss/upgrade` to reproduce the issue.
The fix was validated with a local `bun` run of the upgrade tool:

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 upgrade tool to make sure that newly upgraded
`--spacing(2)` CSS functions is pretty printed to prevent unambiguous
looking classes (even though it compiles correctly).
If you have a class such as `m-[calc(100dvh-theme(spacing.2))]`, then we
used to convert it to `m-[calc(100dvh-calc(var(--spacing)*2))]`. But
recently we introduced the `--spacing(2)` CSS function which means that
the output now looks like this instead: `m-[calc(100dvh---spacing(2))]`.
The triple `-` is valid because the first `-` is the minus sign, the
next two `-` characters are from the function.
One solution is to introduce spaces via underscores:
```
m-[calc(100dvh_-_--spacing(2))]
```
But a simpler solution, is to wrap the `--spacing(2)` in parens to
remove the underscores and improve the readability of the `---`
characters.
```
m-[calc(100dvh-(--spacing(2)))]
```
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.
`overflow-clip` was the name for `text-clip` in v4. However, that was
changed in v3 already so in v3 `overflow-clip` is already doing the same
as in v4. Hence a codemod is not necessary.
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Resolves#15193
This PR fixes an issue where `group` and `peer` would not have their
prefixes migrated as part of the upgrade script. We do this by
registering `group` and `peer` as utilities during the codemods. This
way, `parseCandidate` will find these classes to be valid Tailwind
candidates and the prefix can be migrated just like any other utility.
## Test Plan
Tried it with the v3 upgrade playground in the repo and it worked fine:
<img width="1257" alt="Screenshot 2024-11-27 at 12 17 25"
src="https://github.com/user-attachments/assets/1ee101e1-1d6a-4ce0-b0d4-8d51e5f6b0d2">
I've also added tests to our prefix upgrade integration test and the
prefix migration unit tests.
This PR ensures that when we inject `layer(…)` into an `@import` that it
always gets inserted as the first param. The `layer(…)` has to come
first by spec.
Input:
```css
@import "./foo" supports(--foo);
```
Before:
```css
@import "./foo" supports(--foo) layer(utilities);
```
After:
```css
@import "./foo" layer(utilities) supports(--foo);
```
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>
This PR improves compatibility for named `opacity` theme values. One of
the changes in v4 is that we use the CSS `color-mix()` function to apply
opacity to colors. This, however, is limited to percentage values only:
```css
color: color-mix(in oklch, var(--color-red-500) 50%, transparent);
/* ^^^ */
/* This needs to be a percentage value */
```
In v3, however, it was common to specify custom opacity values as
decimal numbers. That's also what we did in our default config:
6069a81187/stubs/config.full.js (L703-L725)
This PR improves interop with these values by:
1. Converting decimal numbers in the range of `[0, 1]` to their
percentage value equivalent when using the interop layer.
2. Adjusts the codemod that migrates `opacity` theme keys to Tailwind v4
theme variables to include the same conversion.
3. Furthermore, due to the added support of named opacity modifers, we
can also drop theme keys that would now be the default. For example the
following config would not be necessary in v4 as the opacity modifier
would accept the value `50` by default:
```js
module.exports = {
theme: {
opacity: {
50: 0.5
}
}
}
```
## Test Plan
Added a new integration test for the codemod and a unit test for the
interop layer. I also re-ran the codemod on the Commit template and it's
now working as expected (left is v3, right is v4):
<img width="2560" alt="Screenshot 2024-11-21 at 17 25 32"
src="https://github.com/user-attachments/assets/f0c87243-ca80-4c39-ae5e-c1ab48fbe614">
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 makes the candidate parser more strict by not allowing empty
arbitrary values.
Examples that are not allowed anymore:
- `bg-[]` — arbitrary value
- `bg-()` — arbitrary value, var shorthand
- `bg-[length:]` — arbitrary value, with typehint
- `bg-(length:)` — arbitrary value, with typehint, var shorthand
- `bg-red-500/[]` — arbitrary modifier
- `bg-red-500/()` — arbitrary modifier, var shorthand
- `data-[]:flex` — arbitrary value for variant
- `data-():flex` — arbitrary value for variant, var shorthand
- `group-visible/[]:flex` — arbitrary modifier for variant
- `group-visible/():flex` — arbitrary modifier for variant, var
shorthand
If you are trying to trick the parser by injecting some spaces like
this:
- `bg-[_]`
Then that is also not allowed.
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>
While testing the codemods on some projects, I noticed some issues with
the migration to the new `in-*` variant.
One such example is that we checked for `&` at the end, instead of ` &`
(the whitespace is significant).
This meant that `[figure>&]:my-0` was converted to `in-[figure>]:my-0`
which is wrong. In this case, we want to keep it as `[figure>&]:my-0`.
Additionally this PR brings back the migration from `group-[]:flex` to
`in-[.group]:flex`. If you are using a prefix, then `group-[]:tw-flex`
is migrated to `tw:in-[.tw\:group]:flex`.
Last but not least, this does some internal refactors to group
migrations logically together.
This PR fixes an issue where the Tailwind root file detection was wrong.
Whenever a CSS file contains any of the `@tailwind` directives or an
`@import` to any of the Tailwind files, the file is considered a
Tailwind root file.
If multiple CSS files are part of the same tree, then we make the
nearest common parent the root file.
This root file will be the file where we add `@config` and/or inject
other changes during the migration process.
However, if your folder structure looked like this:
```css
/* index.css */
@import "./base.css";
@import "./typography.css";
@import "tailwindcss/components"; /* This makes index.css a root file */
@import "./utilities.css";
/* base.css */
@tailwind base; /* This makes base.css a root file */
/* utilities.css */
@tailwind utilities; /* This makes utilities.css a root file */
```
Then we computed that `index.css` nad `base.css` were considered root
files even though they belong to the same tree (because `base.css` is
imported by `index.css`).
This PR fixes that behaviour by essentially being less smart, and just
checking again if any sheets are part of the same tree.
# Test plan:
Added an integration test that covers this scenario and fails before the
fix.
Also ran it on our tailwindcss.com codebase.
| Before | After |
| --- | --- |
| <img width="1072" alt="image"
src="https://github.com/user-attachments/assets/8ee99a59-335e-4221-b368-a8cd81e85191">
| <img width="1072" alt="image"
src="https://github.com/user-attachments/assets/fe5acae4-d3fc-43a4-bd31-eee768a3a6a5">
|
(Yes, I know the migration still fails, but that's a different issue.)