## 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
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.
### What changed?
#### ✳️ dedent (1.5.3 → 1.6.0) · [Repo](https://github.com/dmnd/dedent)
· [Changelog](https://github.com/dmnd/dedent/blob/main/CHANGELOG.md)
<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/dmnd/dedent/releases/tag/v1.6.0">1.6.0</a></h4>
<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>feat: add <code class="notranslate">trimWhitespace</code> option by
<a href="https://bounce.depfu.com/github.com/43081j">@43081j</a> in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/97">#97</a>
</li>
</ul>
<h2 dir="auto">New Contributors</h2>
<ul dir="auto">
<li>
<a href="https://bounce.depfu.com/github.com/43081j">@43081j</a> made
their first contribution in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/97">#97</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/compare/v1.5.3...v1.6.0"><tt>v1.5.3...v1.6.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="https://depfu.com/packages/npm/dedent/feedback">Please let us
know.</a></em></p>
</details>
<details>
<summary>Commits</summary>
<p><a
href="90644fe0be...ab2ce25762">See
the full diff on Github</a>. The new version differs by 2 commits:</p>
<ul>
<li><a
href="ab2ce25762"><code>1.6.0
(#98)</code></a></li>
<li><a
href="86902f7c97"><code>feat:
add `trimWhitespace` option (#97)</code></a></li>
</ul>
</details>
---

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.
<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
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>
Prepare the 4.0.16 release.
~~Also added a commit to mark the `--value('…')` and `--modifier('…')`
with literals strings as an experimental feature (aka not shipped in
this PR). But we can revert that commit if we still want to ship it in
4.0.16 instead of 4.1.~~
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
<!--
👋 Hey, thanks for your interest in contributing to Tailwind!
**Please ask first before starting work on any significant new
features.**
It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.
https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md
-->
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.
<!--
👋 Hey, thanks for your interest in contributing to Tailwind!
**Please ask first before starting work on any significant new
features.**
It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.
https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md
-->
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR fixes an issue where installing a specific version of
`@tailwindcss/postcss` and `tailwindcss` could still result in a version
mismatch. This is because we were relying on `^4.0.6` for example
instead of `4.0.6`.
This PR now pins all these versions to prevent this:
```
❯ pnpm why tailwindcss
devDependencies:
@tailwindcss/postcss 4.0.5
├─┬ @tailwindcss/node 4.0.6
│ └── tailwindcss 4.0.6
└── tailwindcss 4.0.5
```