This PR is a follow up of #18815 and #18816, but this time let's migrate
the `supports` theme keys.
Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
content: ['./src/**/*.html'],
theme: {
extend: {
supports: {
// Automatically handled by bare values (using CSS variable as the value)
foo: 'foo: var(--foo)', // parentheses are optional
bar: '(bar: var(--bar))',
// Not automatically handled because names differ
baz: 'qux: var(--foo)',
// ^^^ ^^^ ← different names
// Custom
grid: 'display: grid',
},
},
},
}
```
Then we would generate the following Tailwind CSS v4 CSS:
```css
@custom-variant supports-baz {
@supports (qux: var(--foo)) {
@slot;
}
}
@custom-variant supports-grid {
@supports (display: grid) {
@slot;
}
}
```
Notice how we didn't generate a custom variant for `data-foo` or
`data-bar` because those are automatically handled by bare values.
I also went with the longer form of `@custom-variant`, we could use the
single selector approach, but that felt less clear to me.
```css
@custom-variant supports-baz (@supports (qux: var(--foo)));
@custom-variant supports-grid (@supports (display: grid));
```
---------
Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
This PR is similar to and a follow up of #18815, but this time to
migrate the `data` theme keys.
Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
content: ['./src/**/*.html'],
theme: {
extend: {
data: {
// Automatically handled by bare values
foo: 'foo',
// ^^^ ^^^ ← same names
// Not automatically handled by bare values
bar: 'baz',
// ^^^ ^^^ ← different names
// Completely custom
checked: 'ui~="checked"',
},
},
},
}
```
Then we would generate the following Tailwind CSS v4 CSS:
```css
@custom-variant data-bar (&[data-baz]);
@custom-variant data-checked (&[data-ui~="checked"]);
```
Notice how we didn't generate a custom variant for `data-foo` because
those are automatically handled by bare values.
This PR migrates `aria` theme keys when migrating from Tailwind CSS v3
to v4.
While working on improving some of the error messages to get more
insights into why migrating the JS file changed
(https://github.com/tailwindlabs/tailwindcss/pull/18808), I ran into an
issue where I couldn't think of a good comment to why `aria` theme keys
were not being migrated. (Internally we have `aria` "blocked").
So instead of figuring out a good error message..., I just went ahead
and added the migration for `aria` theme keys.
Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
content: ['./src/**/*.html'],
theme: {
extend: {
aria: {
// Built-in (not really, but visible because of intellisense)
busy: 'busy="true"',
// Automatically handled by bare values
foo: 'foo="true"',
// ^^^ ^^^ ← same names
// Not automatically handled by bare values because names differ
bar: 'baz="true"',
// ^^^ ^^^ ← different names
// Completely custom
asc: 'sort="ascending"',
desc: 'sort="descending"',
},
},
},
}
```
Then we would generate the following Tailwind CSS v4 CSS:
```css
@custom-variant aria-bar (&[aria-baz="true"]);
@custom-variant aria-asc (&[aria-sort="ascending"]);
@custom-variant aria-desc (&[aria-sort="descending"]);
```
Notice how we didn't generate a custom variant for `aria-busy` or
`aria-foo` because those are automatically handled by bare values.
We could also emit a comment near the CSS to warn about the fact that
`@custom-variant` will always be sorted _after_ any other built-in
variants.
This could result in slightly different behavior, or different order of
classes when using `prettier-plugin-tailwindcss`.
I don't know how important this is, because before this PR we would just
use `@config './tailwind.config.js';`.
Edit: when using the `@config` we override `aria` and extend it, which
means that it would be in the expected order 🤔
---------
Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
This PR fixes 2 false-positives when running the upgrade tool on a
Tailwind CSS v3 project converting it to a Tailwind CSS v4 project.
The issue occurs around migrations with short simple names that have a
meaning outside if Tailwind CSS, e.g. `blur` and `outline`.
This PR fixes 2 such cases:
1. The `addEventListener` case:
```js
document.addEventListener('blur', handleBlur)
```
We do this by special casing the `addEventListener(` case and making
sure the first argument to `addEventListener` is never migrated.
2. A JavaScript variable with default value:
```js
function foo({ foo = "bar", outline = true, baz = "qux" }) {
// ...
}
```
The bug is relatively subtle here, but it has actually nothing to do
with `outline` itself, but rather the fact that some quote character
came before and after it on the same line...
One of our heuristics for determining if a migration on these small
words is safe, is to ensure that the candidate is inside of a string.
Since we didn't do any kind of quote balancing, we would consider the
`outline` to be inside of a string, even though it is not.
So to actually solve this, we do some form of quote balancing to ensure
that it's _not_ inside of a string in this case.
Additionally, this PR also introduces a small refactor to the
`is-safe-migration.test.ts` file where we now use a `test.each` to
ensure that failing tests in the middle don't prevent the rest of the
tests from running.
### Test plan
1. Added dedicated tests for the cases mentioned in the issue (#18675).
2. Added a few more tests with various forms of whitespace.
Fixes: #18675
Fixes#18400
In v3 when you used `important: true` it did not affect `@apply`.
However, in v4 it does and there's no way to make it *not*. This is
definitely a bug and would be unexpected for users coming from v3 who
use `@apply` and `important` together.
Basically, the following code, along with the detected utility `flex` in
source files…
```css
@import 'tailwindcss/utilities' important;
.flex-explicitly-important {
@apply flex!;
}
.flex-not-important {
@apply flex;
}
```
… would output this:
```css
.flex {
display: flex !important;
}
.flex-explicitly-important {
display: flex !important;
}
.flex-not-important {
display: flex !important;
}
```
But it's expected that `@apply` doesn't consider the "global" important
state. This PR addresss this problem and now the output is this:
```css
.flex {
display: flex !important;
}
.flex-explicitly-important {
display: flex !important;
}
.flex-not-important {
display: flex; /* this line changed */
}
```
If you want to mark a utility as important in `@apply` you can still use
`!` after the utility to do so as shown above.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This pull request contains a couple of minor documentation fixes.
- Corrected a typo from `predicable` to `predictable` in a comment for
`DarkModeStrategy`.
- Applied minor formatting to a comment in a test file.
These changes help improve code clarity and maintainability.
Description:
This pull request corrects minor typos in comments and improves clarity
in two files:
- Fixes a typo in a comment within migrate-js-config.ts ("migrateable" →
"migratable").
- Refines a comment in wasm.test.ts for better readability.
No functional code changes are included.
## 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"
/>
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>
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]
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.
This PR replaces `@variant` with `@custom-variant` for registering
custom variants via your CSS.
In addition, this PR introduces `@variant` that can be used in your CSS
to use a variant while writing custom CSS.
E.g.:
```css
.btn {
background: white;
@variant dark {
background: black;
}
}
```
Compiles to:
```css
.btn {
background: white;
}
@media (prefers-color-scheme: dark) {
.btn {
background: black;
}
}
```
For backwards compatibility, the `@variant` rules that don't have a body
and are
defined inline:
```css
@variant hocus (&:hover, &:focus);
```
And `@variant` rules that are defined with a body and a `@slot`:
```css
@variant hocus {
&:hover, &:focus {
@slot;
}
}
```
Will automatically be upgraded to `@custom-variant` internally, so no
breaking changes are introduced with this PR.
---
TODO:
- [x] ~~Decide whether we want to allow multiple variants and if so,
what syntax should be used. If not, nesting `@variant <variant> {}` will
be the way to go.~~ Only a single `@variant <variant>` can be used, if
you want to use multiple, nesting should be used:
```css
.foo {
@variant hover {
@variant focus {
color: red;
}
}
}
```
This PR improves the codemod tool to simplify 2 things:
1. Whenever you have a `theme(…)` call, we try to change it to a
`var(…)`, but if that doesn't work for some reason, we will make sure to
at least convert it to the more modern `--theme(…)`.
2. When converting `theme(spacing.2)`, we used to convert it to
`calc(var(--spacing)*2)`, but now we will convert it to `--spacing(2)`
instead.
This PR 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>
While testing the latest alpha release across Tailwind v3 projects, we
noticed one regression in relation to the default color of `<button>`
elements. In v3, the reset would change the default to `inherit` but in
v4 we would _not include it in the reset snippet inserted by the upgrade
too_.
This PR changes the upgrade snippet to include it:
```diff
/*
In Tailwind CSS v4, basic styles are applied to form elements by default. To
maintain compatibility with v3, the following resets have been added:
*/
@layer base {
input,
textarea,
select,
button {
border: 0px solid;
border-radius: 0;
padding: 0;
+ color: inherit;
background-color: transparent;
}
}
```
This PR also ensures that there's a newline between the two code
snippets.
## Test Plan
### Before

### After
<img width="1354" alt="Screenshot 2024-11-21 at 15 42 58"
src="https://github.com/user-attachments/assets/9a4503fe-683f-4d08-abf2-7dd111ed5428">
This PR introduces consistent base styles for buttons and form controls
in Tailwind CSS v4.
## Motivation
In v3, form elements lack default styles, which can be
confusing—especially when certain elements, like a text input without a
placeholder or value, are rendered completely invisible on the page.
The goal of this change is to provide reasonable default styles for
buttons, inputs, selects, and textareas that are (mostly) consistent
across all browsers while remaining easy to customize with your own
styles.
This improvement should make Tailwind more accessible for developers new
to the framework and more convenient in scenarios where you need to
quickly create demos (e.g., using Tailwind Play).
## Light and dark mode support
These styles support both light and dark mode, achieved using the
`light-dark()` CSS function. While browser support for this function is
still somewhat limited, Lightning CSS transpiles it to a CSS
variable-based approach that works in older browsers.
For this approach to function correctly, a default `color-scheme` must
be set in your CSS (as explained in [the Lightning CSS
documentation](https://lightningcss.dev/transpilation.html#light-dark()-color-function)).
This PR addresses this requirement by setting the `color-scheme` to
`light` on the `html` element in Preflight.
<img width="1712" alt="image"
src="https://github.com/user-attachments/assets/dba56368-1427-47b3-9419-7c2f6313a944">
<img width="1709" alt="image"
src="https://github.com/user-attachments/assets/3d84fcd2-9606-4626-8e03-164a1dce9018">
## Breaking changes
While we don’t expect these changes to significantly impact v3 users
upgrading to v4, there may be minor differences for those relying on the
simpler v3 styles.
For example, Preflight now applies a `border-radius` to buttons and form
controls. If you weren’t explicitly setting the border radius to `0` in
your project, you’ll need to do so to restore the previous look.
Thankfully, reverting to the v3 styles is straightforward—just add the
following reset to your CSS:
```css
@layer base {
input,
textarea,
select,
button {
border: 0px solid;
border-radius: 0;
padding: 0;
background-color: transparent;
}
}
```
It’s worth noting that this reset doesn't touch the
`::file-selector-button` styles that were added in this PR. This is
because it's not possible to reliably "undo" these styles and restore
the original user-agent styles (which is what was used in v3), as these
are different in each browser. However, these new styles actually match
the defaults in most browsers pretty closely, so hopefully this just
won't be an issue.
## Codemod
This PR includes a codemod that automatically inserts the above
mentioned v3 reset to help avoid breaking changes during the upgrade.
The codemod will insert the following CSS:
```css
/*
In Tailwind CSS v4, basic styles are applied to form elements by default. To
maintain compatibility with v3, the following resets have been added:
*/
@layer base {
input,
textarea,
select,
button {
border: 0px solid;
border-radius: 0;
padding: 0;
background-color: transparent;
}
}
```
## Testing
These changes have been tested across a wide range of browsers,
including Chrome, Safari, Firefox, Edge, and Opera on macOS and Windows,
as well as Safari, Chrome, Firefox, and several lesser-known browsers on
iOS and Android.
However, some quirks still exist in certain mobile browsers, such as iOS
Safari, which adds too much bottom padding below date and time inputs:
<img width="1548" alt="Screenshot 2024-11-20 at 3 57 20 PM"
src="https://github.com/user-attachments/assets/507c7724-ac41-4634-a2b3-61ac4917ebce">
The only reliable way to address these issues is by applying
`appearance: none` to these form controls. However, this felt too
opinionated for Preflight, so we’ve opted to leave such adjustments to
user-land implementations.
---------
Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
We noticed that in the current alpha 34 release, the `package.json` file
of the `@tailwindcss/node` package only defines `tailwindcss` as a dev
dependency. This makes it very easy for version mismatches to happen
when a v3 version (or an earlier v4 alpha for that matter) was installed
in the same project:
```json
{
"name": "@tailwindcss/node",
"version": "4.0.0-alpha.34",
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/tailwindlabs/tailwindcss.git",
"directory": "packages/@tailwindcss-node"
},
"bugs": "https://github.com/tailwindlabs/tailwindcss/issues",
"homepage": "https://tailwindcss.com",
"files": [
"dist/"
],
"publishConfig": {
"provenance": true,
"access": "public"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./require-cache": {
"types": "./dist/require-cache.d.ts",
"default": "./dist/require-cache.js"
},
"./esm-cache-loader": {
"types": "./dist/esm-cache.loader.d.mts",
"default": "./dist/esm-cache.loader.mjs"
}
},
"devDependencies": {
"tailwindcss": "4.0.0-alpha.34"
},
"dependencies": {
"enhanced-resolve": "^5.17.1",
"jiti": "^2.0.0-beta.3"
},
"scripts": {
"build": "tsup-node",
"dev": "pnpm run build -- --watch"
}
}
```
Furthermore, we were trying to fix issues where our integration test
setup could not install `tailwindcss@3` because of how we did pnpm
overrides.
This PR fixes this by:
- Ensuring every client that calls into `tailwindcss` core marks it as a
version-pinned dependency. You are still required to install
`tailwindcss` in your project along side a client (e.g.
`@tailwindcss/vite`) but we now only use your installed version for
importing the respective `.css` files. For the core logic, we are now
requiring each package to use `tailwindcss` at the same version. This
should help resolve issues like
https://github.com/tailwindlabs/tailwindcss/discussions/14652
- We tried to eliminate the dependency on `tailwindcss` from the
`@tailwindcss/upgrade` package. Unfortunately this is not possible to do
right now because we need to load the CSS files from v4 to create the
right environment. In a future version we could bundle the required CSS
files with `@tailwidncss/upgrade` but it doesn't seem necessary for now.
- We then changed our integration test overrides to only override the
`tailwindcss` package that are dependencies of the known list of
packages that we have `tailwindcss` dependencies on: `@tailwindcss/node`
and `@tailwindcss/upgrade`. This ensures that we can install v3 of
`tailwindcss` in the integration tests and it will work. Something we
want to do for some upgrade tests.
# Test plan
Integration work again. Furthermore we added a quick setup with the CLI
using the local tarballs and ensured it works:
```bash
pnpm init
pnpm install ../../tailwindcss/dist/tailwindcss-cli.tgz
pnpm install ../../tailwindcss/dist/tailwindcss.tgz
echo '@import "tailwindcss";' > index.css
echo '<div class="underline"></div>' > index.html
pnpm tailwindcss -i index.css -o out.css
cat out.css
```
This PR fixes an issue where imports above Tailwind directives didn't
get a `layer(…)` argument.
Given this CSS:
```css
@import "./typography.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
```
It was migrated to:
```css
@import "./typography.css";
@import "tailwindcss";
```
But to ensure that the typography styles end up in the correct location,
it requires the `layer(…)` argument.
This PR now migrates the input to:
```css
@import "./typography.css" layer(base);
@import "tailwindcss";
```
Test plan:
---
Added an integration test where an import receives the `layer(…)`, but
an import that eventually contains `@utility` does not receive the
`layer(…)` argument. This is necessary otherwise the `@utility` will be
nested when we are processing the inlined CSS.
Running this on the Commit template, we do have a proper `layer(…)`
<img width="585" alt="image"
src="https://github.com/user-attachments/assets/538055e6-a9ac-490d-981f-41065a6b59f9">
This PR fixes an issue where an `@config` was injected in a strange
location if you have multiple CSS files with Tailwind directives.
Let's say you have this setup:
```css
/* ./src/index.css */
@import "./tailwind-setup.css";
/* ./src/tailwind-setup.css */
@import "./base.css";
@import "./components.css";
@import "./utilities.css";
/* ./src/base.css */
@tailwind base;
/* ./src/components.css */
@tailwind components;
/* ./src/utilities.css */
@tailwind utilities;
```
In this case, `base.css`, `components.css`, and `utilities.css` are all
considered Tailwind roots because they contain Tailwind directives or
imports.
Since there are multiple roots, the nearest common ancestor should
become the tailwind root (where `@config` is injected). In this case,
the nearest common ancestor is `tailwind-setup.css` (not `index.css`
because that's further away).
Before this change, we find the common ancestor between `base.css` and
`components.css` which would be `index.css` instead of
`tailwind-setup.css`.
In a next iteration, we compare `index.css` with `utilities.css` and
find that there is no common ancestor (because the `index.css` file has
no parents). This resulted in the `@config` being injected in
`index.css` and in `utilities.css`.
Continuing with the rest of the migrations, we migrate the `index.css`'s
`@config` away, but we didn't migrate the `@config` from
`utilities.css`.
With this PR, we don't even have the `@config` in the `utilities.css`
file anymore.
Test plan
---
1. Added an integration test with a non-migrateable config file to
ensure that the `@config` is injected in the correct file.
2. Added an integration test with a migrateable config file to ensure
that the CSS config is injected in the correct file. h/t @philipp-spiess
3. Ran the upgrade on the https://commit.tailwindui.com project and
ensured that
1. The `@config` does not exist in the `utilities.css` file (this was
the first bug we solved)
2. The `@config` is replaced in the `tailwind.css` file correctly.
<img width="592" alt="image"
src="https://github.com/user-attachments/assets/02e3f6ea-a85d-46c2-ac93-09f34ac4a4b8">
<img width="573" alt="image"
src="https://github.com/user-attachments/assets/e372eb5f-5732-4052-ab39-096ba7970ff6">
This PR fixes an issue where we migrated classes such as `rounded` to
`rounded-sm` (see:
https://github.com/tailwindlabs/tailwindcss/pull/14875)
However, if you override the values in your `tailwind.config.js` file,
then the migration might not be correct.
This PR makes sure to only migrate the classes if you haven't overridden
the values in your `tailwind.config.js` file.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR reverts a change we made for v4 that added borders to inputs by
default. It feels like we have to go further than this for this to
actually be useful to anyone, and since there were no borders in v3 it's
also a breaking change.
If we wanted to make form elements look more "normal" out of the box I
think we need to do something more like this:
https://play.tailwindcss.com/icCwFLVp4z?file=css
But it's a huge rabbit hole and there are so many stupid details to get
right that it feels like an insurmountable task, and if we can't go all
the way with it it's better to just maximize compatibility with v3.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
We noticed an issue that the `theme()` function wourld not properly
parse in CSS if you split the argument list over multiple lines. This is
fixed by treating `\n` and `\t` the same as space:
```css
.custom-font {
font-family: theme(
fontFamily.unknown,
Helvetica Neue,
Helvetica,
sans-serif
);
}
```
## Test plan
Added tests, but also tried it in the Vite example:
<img width="1995" alt="Screenshot 2024-11-08 at 13 46 09"
src="https://github.com/user-attachments/assets/f9bf94b0-3f9b-4334-8911-9190987e2df5">
During the migration process, a lot of changes to the CSS file happen.
Some parts are converted, some parts are deleted and some new CSS is
added.
To make sure we are generating a sensible and good looking CSS file, we
will sort the final CSS and pretty print it.
The order we came up with looks like this:
```css
/* Imports */
@import "tailwindcss";
@import "../other.css";
/* Configuration */
@config "../path/to/tailwindcss.config.js";
@plugin "my-plugin-1";
@plugin "my-plugin-2";
@source "./foo/**/*.ts";
@source "./bar/**/*.ts";
@variant foo {}
@variant bar {}
@theme {}
/* Border compatibility CSS */
@layer base {}
/* Utilities */
@utility foo {}
@utility bar {}
/* Rest of your own CSS if any */
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR replaces the default spacing scale (`--spacing-*`) with a
generative system based on a default spacing _unit_.
Instead of the default theme containing values like `--spacing-4`,
`--spacing-6`, `--spacing-8`, etc., instead we just define a single
`--spacing` value:
```css
@theme {
--spacing: 0.25rem;
}
```
Utilities like `px-4` are derived from this unit by multiplying it by
the value in the utility (4 in this case):
```css
.px-4 {
padding-inline: calc(var(--spacing) * 4);
}
```
The biggest consequence of this change is that every value is available
now, rather than just the explicitly configured values.
This means utilities like `px-42` will work now, whereas prior to this
PR only `px-40` and `px-44` were valid utilities. I personally found it
very difficult to know which values actually existed at the higher end
of the scale without IntelliSense, and in practice even when working
with a skilled designer like [Steve](https://x.com/steveschoger) who
helped design Tailwind's default spacing scale, I'd very often need to
break out of it to implement a design, and trying to round to a value
that was in the scale made the design worse, not better.
This PR allows you to use any whole number, as well as decimal numbers
that are multiples of `0.25` to ensure classes like `px-1.5` continue to
work. While this means you can now technically do things like
`pt-97.25`, I think the presence of the fractional value will be enough
of a signal to developers that they are doing something a little
unusual, and they can use their judgment as to whether they are making
the right decision or not.
I'll update this PR with a lot more detail when I have a chance, as
there are a few other things to explain like:
- Unifying all of the values for
width/min-width/max-width/height/min-height/max-height utilities
- Deriving numeric line-height values from the spacing multiplier
instead of a separate line-height scale
- Using `--spacing: initial` to disable the multiplier
- How you can still use an explicit spacing scale and ignore this change
- How we plan to use IntelliSense to surface a more curated set of
spacing values even if smaller increments work when you type them
explicitly
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
When migrating a project from Tailwind CSS v3 to Tailwind CSS v4, then
we started the migration process in the following order:
1. Migrate the JS/TS config file
2. Migrate the source files (found via the `content` option)
3. Migrate the CSS files
However, if you have a setup where you have multiple CSS root files
(e.g.: `frontend` and `admin` are separated), then that typically means
that you have an `@config` directive in your CSS files. These point to
the Tailwind CSS config file.
This PR changes the migration order to do the following:
1. Build a tree of all the CSS files
2. For each `@config` directive, migrate the JS/TS config file
3. For each JS/TS config file, migrate the source files
If a CSS file does not contain any `@config` directives, then we start
by filling in the `@config` directive with the default Tailwind CSS
config file (if found, or the one passed in). If no default config file
or passed in config file can be found, then we will error out (just like
we do now)
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR fixes an issue where `layer(…)` next to imports were removed
where they shouldn't have been removed.
The issue exists if _any_ of the `@import` nodes in a file contains
`@utility`, if that's the case then we removed the `layer(…)` next to
_all_ `@import` nodes.
Before we were checking if the current sheet contained `@utility` or in
any of its children (sub-`@import` nodes).
This fixes that by looping over the `@import` nodes in the current
sheet, and looking for the `@utility` in the associated/imported file.
This way we update each node individually.
Test plan:
---
Added a dedicated integration test to make sure all codemods together
result in the correct result. Input:
96e8908378/integrations/upgrade/index.test.ts (L2076-L2108)
Output:
96e8908378/integrations/upgrade/index.test.ts (L2116-L2126)
This PR improves where we inject the border compatibility CSS. Before
this change we injected it if it was necessary in one of these spots:
- Above the first `@layer base` to group it together with existing
`@layer base` at-rules.
- If not present, after the last `@import`, to make sure that we emit
valid CSS because `@import` should be at the top (with a few
exceptions).
However, if you are working with multiple CSS files, then it could be
that we injected the border compatibility CSS multiple times if those
files met one of the above conditions.
To solve this, we now inject the border compatibility CSS with the same
rules as above, but we also have another condition:
The border compatibility CSS is only injected if the file also has a
`@import "tailwindcss";` _or_ `@import "tailwindcss/preflight";` in the
current file.
---
Added integration tests to make sure that we are generating what we
expect in a real environment. Some of the integration tests also use the
old `@tailwind` directives to make sure that the order of migrations is
correct (first migrate to `@import` syntax, then inject the border
compatibility CSS).
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
When we implemented the CSS import resolution system, we found out a
detail about CSS imports in that files without a relative path prefix
would still be relative to the source file. E.g.:
```css
@import 'foo.css';
```
Should first look for the file `foo.css` in the same directory. To make
this cost as cheap as possible, we limited this by a heuristics to only
apply the auto-relative imports for files with a file extension.
Naturally, while testing v4 on more templates, we found that it's common
for people to omit the file extension when loading css file. The above
could also be written as such:
```css
@import 'foo';
```
To improve this, we have two options:
- We either remove the heuristics, making every `@import` more expensive
because we have to check for relative files.
- We upgrade our codemods to rewrite `@import` statements to be
explicitly relative.
Because we really care about performance, we opted to go with the latter
option. This PR adds the codemod and removes the heuristics so we
resolve CSS files similar to how you would resolve JS files.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR adds a codemod that ensures that the border styles from Tailwind CSS v3 work as expected once your project is migrated to Tailwind CSS v4.
In Tailwind CSS v3, the default border color is `colors.gray.200` and in Tailwind CSS v4 the default border color is `currentColor`.
Similarly in Tailwind CSS v3, DOM elements such as `input`, `select`, and `textarea` have a border width of `0px`, in Tailwind CSS v4, we don't change the border width of these elements and keep them as `1px`.
If your project happens to already use the same value for the default border color (`currentColor`) as we use in Tailwind CSS v4, then nothing happens. But this is very unlikely, so we will make sure that we honor your `borderColor.DEFAULT` value.
If you didn't change the default values in your `tailwind.config.js`, then we will inject compatibility CSS using the default Tailwind CSS v3 values to ensure the default color and width are applied correctly.
This PR adds a codemod for migrating the old `@screen` directive from Tailwind
CSS v2 that also worked in Tailwind CSS v3 but wasn't documented anymore.
Internally, this first migrates `@screen md` to `@media screen(md)`, then we rely on the existing migration that migrates the `screen(…)` function.
Input:
```css
@screen md {
.foo {
color: red;
}
}
```
Output (IR):
```css
@media screen(md) {
.foo {
color: red;
}
}
```
Output:
```css
@media theme(--breakpoint-md) {
.foo {
color: red;
}
}
```