This PR fixes an issue where the Tailwind root file detection was wrong.
Whenever a CSS file contains any of the `@tailwind` directives or an
`@import` to any of the Tailwind files, the file is considered a
Tailwind root file.
If multiple CSS files are part of the same tree, then we make the
nearest common parent the root file.
This root file will be the file where we add `@config` and/or inject
other changes during the migration process.
However, if your folder structure looked like this:
```css
/* index.css */
@import "./base.css";
@import "./typography.css";
@import "tailwindcss/components"; /* This makes index.css a root file */
@import "./utilities.css";
/* base.css */
@tailwind base; /* This makes base.css a root file */
/* utilities.css */
@tailwind utilities; /* This makes utilities.css a root file */
```
Then we computed that `index.css` nad `base.css` were considered root
files even though they belong to the same tree (because `base.css` is
imported by `index.css`).
This PR fixes that behaviour by essentially being less smart, and just
checking again if any sheets are part of the same tree.
# Test plan:
Added an integration test that covers this scenario and fails before the
fix.
Also ran it on our tailwindcss.com codebase.
| Before | After |
| --- | --- |
| <img width="1072" alt="image"
src="https://github.com/user-attachments/assets/8ee99a59-335e-4221-b368-a8cd81e85191">
| <img width="1072" alt="image"
src="https://github.com/user-attachments/assets/fe5acae4-d3fc-43a4-bd31-eee768a3a6a5">
|
(Yes, I know the migration still fails, but that's a different issue.)
This implements backwards compatibility for colors that use the old
`<alpha-value>` feature from v3. We can do this by replacing
`<alpha-value>` with `1` because we use `color-mix` to actually apply
opacity modifiers in v4.
In some local testing we ran the `@tailwindcss/upgrade` command twice in
a row. It would be great to get some feedback that this is not working,
so this PR now checks if it can resolve the installed version of
`tailwindcss` and if it can, it requires it to be < 4 (you can bypass
this check with `--force`).
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR improves the discoverability of Tailwind config files when we
are trying to link them to your CSS files.
When you have multiple "root" CSS files in your project, and if they
don't include an `@config` directive, then we tried to find the Tailwind
config file in your current working directory.
This means that if you run the upgrade command from the root of your
project, and you have a nested folder with a separate Tailwind setup,
then the nested CSS file would link to the root Tailwind config file.
Visually, you can think of it like this:
```
.
├── admin
│ ├── src
│ │ └── styles
│ │ └── index.css <-- This will be linked to (1)
│ └── tailwind.config.js (2)
├── src
│ └── styles
│ └── index.css <-- This will be linked to (1)
└── tailwind.config.js (1)
```
If you run the upgrade command from the root of your project, then the
`/src/styles/index.css` will be linked to `/tailwind.config.js` which is
what we expect.
But `/admin/src/styles/index.css` will _also_ be linked to
`/tailwind.config.js`
With this PR we improve this behavior by looking at the CSS file, and
crawling up the parent tree. This mens that the new behavior looks like
this:
```
.
├── admin
│ ├── src
│ │ └── styles
│ │ └── index.css <-- This will be linked to (2)
│ └── tailwind.config.js (2)
├── src
│ └── styles
│ └── index.css <-- This will be linked to (1)
└── tailwind.config.js (1)
```
Now `/src/styles/index.css` will be linked to `/tailwind.config.js`, and
`/admin/src/styles/index.css` will be linked to
`/admin/tailwind.config.js`.
When we discover the Tailwind config file, we will also print a message
to the user to let them know which CSS file is linked to which Tailwind
config file.
This should be a safe improvement because if your Tailwind config file
had a different name, or if it lived in a sibling folder then Tailwind
wouldn't find it either and you already required a `@config "…";`
directive in your CSS file to point to the correct file.
In the unlikely event that it turns out that 2 (or more) CSS files
resolve to the same to the same Tailwind config file, then an upgrade
might not be safe and some manual intervention might be needed. In this
case, we will show a warning about this.
<img width="1552" alt="image"
src="https://github.com/user-attachments/assets/7a1ad11d-18c5-4b7d-9a02-14f0116ae955">
Test plan:
---
- Added an integration test that properly links the nearest Tailwind
config file by looking up the tree
- Added an integration test that resolves 2 or more CSS files to the
same config file, resulting in an error where manual intervention is
needed
- Ran it on the Tailwind UI codebase
Running this on Tailwind UI's codebase it looks like this:
<img width="1552" alt="image"
src="https://github.com/user-attachments/assets/21785428-5e0d-47f7-80ec-dab497f58784">
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR re-introduces the automatic var injection feature.
For some backstory, we used to support classes such as `bg-[--my-color]`
that resolved as-if you wrote `bg-[var(--my-color)]`.
The is issue is that some newer CSS properties accepts dashed-idents
(without the `var(…)`). This means that some properties accept
`view-timeline-name: --my-name;` (see:
https://developer.mozilla.org/en-US/docs/Web/CSS/view-timeline-name).
To make this a tiny bit worse, these properties _also_ accept
`var(--my-name-reference)` where the variable `--my-name-reference`
could reference a dashed-ident such as `--my-name`.
This makes the `bg-[--my-color]` ambiguous because we don't know if you
want `var(--my-color)` or `--my-color`.
With this PR, we bring back the automatic var injection feature as
syntactic sugar, but we use a different syntax to avoid the ambiguity.
Instead of `bg-[--my-color]`, you can now write `bg-(--my-color)` to get
the same effect as `bg-[var(--my-color)]`.
This also applies to modifiers, so `bg-red-500/[var(--my-opacity)]` can
be written as `bg-red-500/(--my-opacity)`. To go full circle, you can
rewrite `bg-[var(--my-color)]/[var(--my-opacity)]` as
`bg-(--my-color)/(--my-opacity)`.
---
This is implemented as syntactical sugar at the parsing stage and
handled when re-printing. Internally the system (and every plugin) still
see the proper `var(--my-color)` value.
Since this is also handled during printing of the candidate, codemods
don't need to be changed but they will provide the newly updated syntax.
E.g.: running this on the Catalyst codebase, you'll now see changes like
this:
<img width="542" alt="image"
src="https://github.com/user-attachments/assets/8f0e26f8-f4c9-4cdc-9f28-52307c38610e">
Whereas before we converted this to the much longer
`min-w-[var(--button-width)]`.
---
Additionally, this required some changes to the Oxide scanner to make
sure that `(` and `)` are valid characters for arbitrary-like values.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR adds an improvement to the upgrade tool to make sure that if you
pass a single CSS file, that the upgrade tool resolves all the imports
in that file and processes them as well.
Test plan:
---
Created a project where `index.css` imports `other.css`. Another
`leave-me-alone.css` is created to proof that this file is _not_
changed. Running the upgrade guide using `index.css` also migrates
`other.css` but not `leave-me-alone.css`.
Here is a video so you don't have to manually create it:
https://github.com/user-attachments/assets/20decf77-77d2-4a7c-8ff1-accb1c77f8c1
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 adds support for handling v3 [`container` customizations
](https://tailwindcss.com/docs/container#customizing). This is done by
adding a custom utility to extend the core `container` utility. A
concrete example can be taken from the added integration test.
### Input
```ts
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.html'],
theme: {
container: {
center: true,
padding: {
DEFAULT: '2rem',
'2xl': '4rem',
},
screens: {
md: '48rem', // Matches a default --breakpoint
xl: '1280px',
'2xl': '1536px',
},
},
},
}
```
### Output
```css
@import "tailwindcss";
@utility container {
margin-inline: auto;
padding-inline: 2rem;
@media (width >= theme(--breakpoint-sm)) {
max-width: none;
}
@media (width >= 48rem) {
max-width: 48rem;
}
@media (width >= 1280px) {
max-width: 1280px;
}
@media (width >= 1536px) {
max-width: 1536px;
padding-inline: 4rem;
}
}
````
## Test Plan
This PR adds extensive tests to the compat layer as part of unit tests.
Additionally it does at a test to the codemod setup that shows that the
right `@utility` code is generated. Furthermore I compared the
implementation against v3 on both the compat layer and the custom
`@utility`:
https://github.com/user-attachments/assets/44d6cbfb-4861-4225-9593-602b719f628f
This PR 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>
Part of the current changes, we also want to make the `--line-height-*`
namespace closer to the utility name so we're renaming it to
`--leading-*`:
```diff
@theme {
- --line-height-none: 1;
- --line-height-tight: 1.25;
- --line-height-snug: 1.375;
- --line-height-normal: 1.5;
- --line-height-relaxed: 1.625;
- --line-height-loose: 2;
/* ... */
+ --leading-none: 1;
+ --leading-tight: 1.25;
+ --leading-snug: 1.375;
+ --leading-normal: 1.5;
+ --leading-relaxed: 1.625;
+ --leading-loose: 2;
/* ... */
}
```
Notice that we did not change the nested values used in the `--text`
type scale, e.g.:
```css
@theme {
/* Type scale */
--text-xs: 0.75rem;
--text-xs--line-height: 1rem;
}
```
These do not refer to the `leading` utility and instead refer to nested
properties so we're keeping those as-is.
## Test Plan
Added cases to the CSS `theme()` variable/JS plugin tests (interop
layer) and the integration tests (codemod layer).
Part of the current changes, we also want to make the
`--letter-spacing-*` namespace closer to the utility name so we're
renaming it to `--tracking-*`:
```diff
@theme {
- --letter-spacing-tighter: -0.05em;
- --letter-spacing-tight: -0.025em;
- --letter-spacing-normal: 0em;
- --letter-spacing-wide: 0.025em;
- --letter-spacing-wider: 0.05em;
- --letter-spacing-widest: 0.1em;
/* ... */
+ --tracking-tighter: -0.05em;
+ --tracking-tight: -0.025em;
+ --tracking-normal: 0em;
+ --tracking-wide: 0.025em;
+ --tracking-wider: 0.05em;
+ --tracking-widest: 0.1em;
/* ... */
}
```
## Test Plan
Added cases to the CSS `theme()` variable/JS plugin tests (interop
layer) and the integration tests (codemod layer).
This PR adds migrations for the recent changes to the `--spacing` scale
done in #12263.
There are a few steps that we do to ensure we have the best upgrade
experience:
- If you are overwriting the `spacing` theme with custom values, we now
check if the new values are multiplies of the default spacing scale.
When they are, we can safely remove the overwrite.
- If you are extending the `spacing` theme, we will unset the default
`--spacing` scale and only use the values you provided.
- Any `theme()` function calls are replaced with `calc(var(--spacing) *
multiplier)` unless the values are extending the default scale.
One caveat here is for `theme()` key which can not be replaced with
`var()` (e.g. in `@media` attribute positions). These will not be able
to be replaced with `calc()` either so the following needs to stay
unmigrated:
```css
@media (max-width: theme(spacing.96)) {
.foo {
color: red;
}
}
```
## Test plan
We are mainly testing two scenarios: The JS config _extends_ the
`spacing` namespace and the JS config _overwrites_ the `spacing`
namespace. For both cases we have added an integration test each to
ensure this works as expected. The test contains a mixture of keys (some
of it matching the default multiples, some don't, some have different
scales, and some use non-numeric identifiers). In addition to asserting
on the created CSS `@theme`, we also ensure that `theme()` calls are
properly replaced.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR updates all of the `--font-size-*` variables to `--text-*`
instead to closer match the utility names.
```diff
@theme {
- --font-size-xs: 0.75rem;
- --font-size-xs--line-height: 1rem;
- --font-size-sm: 0.875rem;
- --font-size-sm--line-height: 1.25rem;
- --font-size-base: 1rem;
- --font-size-base--line-height: 1.5rem;
- --font-size-lg: 1.125rem;
- --font-size-lg--line-height: 1.75rem;
- --font-size-xl: 1.25rem;
- --font-size-xl--line-height: 1.75rem;
/* ... */
+ --text-xs: 0.75rem;
+ --text-xs--line-height: 1rem;
+ --text-sm: 0.875rem;
+ --text-sm--line-height: 1.25rem;
+ --text-base: 1rem;
+ --text-base--line-height: 1.5rem;
+ --text-lg: 1.125rem;
+ --text-lg--line-height: 1.75rem;
+ --text-xl: 1.25rem;
+ --text-xl--line-height: 1.75rem;
/* ... */
}
```
This is part of a bigger set of changes where we're renaming other theme
variables as well with the same goals, since many existing theme
variables like `--shadow-*` and `--radius-*` are already not using the
explicit CSS property name.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR fixes an issue where globs in you `content` configuration escape
the current "root" of the project.
This can happen if you have a folder, and you need to look up in the
tree (e.g.: when looking at another package in a monorepo, or in case of
a Laravel project where you want to look at mail templates).
This applies a similar strategy we already implement on the Rust side.
1. Expand braces in the globs
2. Move static parts of the `pattern` to the `base` of the glob entry
object
---
Given a project setup like this:
```
.
├── admin
│ ├── my-tailwind.config.ts
│ └── src
│ ├── abc.jpg
│ ├── index.html
│ ├── index.js
│ └── styles
│ └── input.css
├── dashboard
│ ├── src
│ │ ├── index.html
│ │ ├── index.js
│ │ ├── input.css
│ │ └── pickaday.css
│ └── tailwind.config.ts
├── package-lock.json
├── package.json
├── postcss.config.js
└── unrelated
└── index.html
7 directories, 14 files
```
If you then have this config:
```ts
// admin/my-tailwind.config.ts
export default {
content: {
relative: true,
files: ['./src/**/*.html', '../dashboard/src/**/*.html'],
// ^^ this is the important part, which escapes
// the current root of the project.
},
theme: {
extend: {
colors: {
primary: 'red',
},
},
},
}
```
Then before this change, running the command looks like this:
<img width="1760" alt="image"
src="https://github.com/user-attachments/assets/60e2dfc7-3751-4432-80e3-8b4b8f1083d4">
After this change, running the command looks like this:
<img width="1452" alt="image"
src="https://github.com/user-attachments/assets/5c47182c-119c-4732-a253-2dace7086049">
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
During the migration process, a lot of changes to the CSS file happen.
Some parts are converted, some parts are deleted and some new CSS is
added.
To make sure we are generating a sensible and good looking CSS file, we
will sort the final CSS and pretty print it.
The order we came up with looks like this:
```css
/* Imports */
@import "tailwindcss";
@import "../other.css";
/* Configuration */
@config "../path/to/tailwindcss.config.js";
@plugin "my-plugin-1";
@plugin "my-plugin-2";
@source "./foo/**/*.ts";
@source "./bar/**/*.ts";
@variant foo {}
@variant bar {}
@theme {}
/* Border compatibility CSS */
@layer base {}
/* Utilities */
@utility foo {}
@utility bar {}
/* Rest of your own CSS if any */
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR renames the `--font-family-*` theme variables to `--font-*` to
more closely match the utility names and be a bit more terse in general.
```diff
@theme {
- --font-family-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
- --font-family-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
- --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
+ --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+ --font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
}
```
This is part of a bigger set of changes where we're renaming other theme
variables as well with the same goals, since many existing theme
variables like `--shadow-*` and `--radius-*` are already not using the
explicit CSS property name.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR adds a migration for migrating the changes we implemented in
https://github.com/tailwindlabs/tailwindcss/pull/14849
This is the migration we perform:
| Old | New |
| ----------------- | ------------------ |
| `shadow` | `shadow-sm` |
| `shadow-sm` | `shadow-xs` |
| `shadow-xs` | `shadow-2xs` |
| `inset-shadow` | `inset-shadow-sm` |
| `inset-shadow-sm` | `inset-shadow-xs` |
| `inset-shadow-xs` | `inset-shadow-2xs` |
| `drop-shadow` | `drop-shadow-sm` |
| `drop-shadow-sm` | `drop-shadow-xs` |
| `rounded` | `rounded-sm` |
| `rounded-sm` | `rounded-xs` |
| `blur` | `blur-sm` |
| `blur-sm` | `blur-xs` |
Also added an integration test to ensure that `shadow` is properly
migrated to `shadow-sm`, and doesn't get migrated to `shadow-xs`
(because `shadow-sm` is migrated to `shadow-xs`).
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 improves the PostCSS migrations to make sure that we install
`@tailwindcss/postcss` in the same bucket as `tailwindcss`.
If `tailwindcss` exists in the `dependencies` bucket, we install
`@tailwindcss/postcss` in the same bucket. If `tailwindcss` exists in
the `devDependencies` bucket, we install `@tailwindcss/postcss` in the
same bucket.
This also contains an internal refactor that normalizes the package
manager to make sure we can install a package to the correct bucket
depending on the package manager.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR adds a migration to bump the `prettier-plugin-tailwindcss`
version to the latest version when upgrading your project. This is to
ensure that the plugin is compatible with the latest version of Tailwind
CSS.
Note: we will only do this _if_ you already used the
`prettier-plugin-tailwindcss` plugin in your project.
This PR is a continuation of #14783 to handle the feedback on that PR.
1. Update the test to be more realistic
2. Updated the comment
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.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>
This PR fixes an issue where currently a `theme()` function call inside
an arbitrary value that used a dot in the key path:
```jsx
let className = "ml-[theme(spacing[1.5])]"
```
Was causing issues when going though the codemod. The issue is that for
candidates, we require `_` to be _escaped_, since otherwise they will be
replaced with underscore. When going through the codemods, the above
candidate would be translated to the following CSS variable access:
```js
let className = "ml-[var(--spacing-1\_5))"
```
Because the underscore was escaped, we now have an invalid string inside
a JavaScript file (as the `\` would escape inside the quoted string.
To resolve this, we decided that this common case (as its used by the
Tailwind CSS default theme) should work without escaping. In
https://github.com/tailwindlabs/tailwindcss/pull/14776, we made the
changes that CSS variables used via `var()` no longer unescape
underscores. This PR extends that so that the Variant printer (that
creates the serialized candidate representation after the codemods make
changes) take this new encoding into account.
This will result in the above example being translated into:
```js
let className = "ml-[var(--spacing-1_5))"
```
With no more escaping. Nice!
## Test Plan
I have added test for this to the kitchen-sink upgrade tests.
Furthermore, to ensure this really works full-stack, I have updated the
kitchen-sink test to _actually build the migrated project with Tailwind
CSS v4_. After doing so, we can assert that we indeed have the right
class name in the generated CSS.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR improves the heuristics around the important codemod (e.g.
`!border` => `border!`) as we noticed a few more cases where we the
current heuristics was not enough.
Specifically, we made it not migrate the candidate in the following
conditions:
- When there's an immediate property access: `{ "foo": !border.something
+ ""}`
- When it's used as condition in the template language: `<div
v-if="something && !border"></div>` or `<div x-if="!border"></div>`
## Test plan
I added test cases to the unit tests and updated the integration test to
contain a more sophisticated example.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR makes sure the `migrateImport` codemod is properly registered so
that it runs as part of the upgrade process.
## Test plan
This PR adds a new `v3` playground with an `upgrade` script that you can
use to run the upgrade from the local package. When you add a
non-prefixed `@import` to the v3 example, the paths are now properly
updated with no errors logged:
https://github.com/user-attachments/assets/85949bbb-756b-4ee2-8ac0-234fe1b2ca39
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR fixes an issue where JS configuration theme properties with dots
or slashes in them would not migrate correctly. E.g.:
```ts
import { type Config } from 'tailwindcss'
module.exports = {
theme: {
width: {
1.5: '0.375rem',
'1/2': '50%',
}
}
}
```
This should convert to:
```css
@theme {
--width-1_5: 0.375rem;
--width-1\/2: 50%;
}
```
_Note: We will likely change the `--width-1_5` key to `--width-1\.5` in
a follow-up PR._
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR adds a codemod that ensures that the border styles from Tailwind CSS v3 work as expected once your project is migrated to Tailwind CSS v4.
In Tailwind CSS v3, the default border color is `colors.gray.200` and in Tailwind CSS v4 the default border color is `currentColor`.
Similarly in Tailwind CSS v3, DOM elements such as `input`, `select`, and `textarea` have a border width of `0px`, in Tailwind CSS v4, we don't change the border width of these elements and keep them as `1px`.
If your project happens to already use the same value for the default border color (`currentColor`) as we use in Tailwind CSS v4, then nothing happens. But this is very unlikely, so we will make sure that we honor your `borderColor.DEFAULT` value.
If you didn't change the default values in your `tailwind.config.js`, then we will inject compatibility CSS using the default Tailwind CSS v3 values to ensure the default color and width are applied correctly.
The important candidate migration is one of the most broad we have since it matches for any utility that are prefixed with an exclamation mark.
When running the codemodes on our example projects, we noticed that this was instead creating false-positives with candidates used in code positions, e.g:
```ts
export default {
shouldNotUse: !border.shouldUse,
}
```
To prevent false-positives, this PR adds a heuristics to detect wether or not a candidate is used in a non-code position. We do this by checking the character before and after the modifier and only allow quotes or spaces.
This can cause candidates to not migrate that are valid Tailwind CSS classes, e.g.:
```ts
let classNames = `!underline${isHovered ? ' font-bold' : ''}`
```
This, however, is not a big issue since v4 can parse the v3 important prefix too.
This PR migrates the `@variants` and `@responsive` directives.
In Tailwind CSS v2, these were used to generate certain variants of responsive variants for the give classes. In Tailwind CSS v3, these still worked but were implemented as a no-op such that these directives don't end up in your final CSS.
In Tailwind CSS v4, these don't exist at all anymore, so we can safely get rid of them by replacing them with their contents.
Input:
```css
@variants hover, focus {
.foo {
color: red;
}
}
@responsive {
.bar {
color: blue;
}
}
```
Output:
```css
.foo {
color: red;
}
.bar {
color: blue;
}
```
We have a migration that adds the `layer(…)` next to the `@import`
depending on the order of original values. For example:
```css
@import "tailwindcss/utilities":
@import "./foo.css":
@import "tailwindcss/components":
```
Will be turned into:
```css
@import "tailwindcss":
@import "./foo.css" layer(utilities):
```
Because it used to exist between `utilities` and `components`. Without
this it would be _after_ `components`.
This results in an issue if an import has (deeply) nested `@utility`
at-rules after migrations. This is because if this is generated:
```css
/* ./src/index.css */
@import "tailwindcss";
@import "./foo.css" layer(utilities);
/* ./src/foo.css */
@utility foo {
color: red;
}
```
Once we interpret this (and thus flatten it), the final CSS would look
like:
```css
@layer utilities {
@utility foo {
color: red;
}
}
```
This means that `@utility` is not top-level and an error would occur.
This fixes that by removing the `layer(…)` from the import if the
imported file (or any of its children) contains an `@utility`. This is
to ensure that once everything is imported and flattened, that all
`@utility` at-rules are top-level.
This PR updates all of our OKCLH colors to use `0` instead of `none` due
to weird behavior in Chrome where using `color-mix` with colors using
`none` produces unexpected results:
<img width="1110" alt="image"
src="https://github.com/user-attachments/assets/2272e494-500b-4f75-b5c1-d41c714f0339">
Both `none` and `0` behave as expected in Safari and Firefox so
suspecting this is a bug in Chrome rather than spec'd behavior.
Fixes#14740
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR changes the migration of `content` rules in the JS config to CSS codemods.
When a `content` rule is processed which matches files that are _also matched by the automatic content discovery in v4_, we do not need to emit CSS for that rule.
Take, for example this v3 configuration file:
```ts
import { type Config } from 'tailwindcss'
module.exports = {
content: [
'./src/**/*.{html,js}',
'./node_modules/my-external-lib/**/*.{html}'
],
} satisfies Config
```
Provided the base directories match up, the first rule will also be covered by the automatic content discovery in v4 and thus we only need to convert the second rule to CSS:
```css
@import "tailwindcss";
@source '../node_modules/my-external-lib/**/*.{html}';
```
This PR extends our JS configuration to CSS migration by also allowing `plugins` with options.
An example of such config would be:
```js
import { type Config } from 'tailwindcss'
import myPlugin from "./myPlugin";
export default {
plugins: [
myPlugin({
class: "tw",
}),
],
} satisfies Config;
```
If the option object contains only values allowed in our CSS API, we can convert this to CSS entirely:
```css
@plugin './myPlugin' {
class: 'tw';
}
```
With the changes in #14672, it now becomes trivial to actually resolve
the config (while still retaining the reset behavior). This means that
we can now convert JS configs that use _functions_, e.g.:
```ts
import { type Config } from 'tailwindcss'
export default {
theme: {
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
},
} satisfies Config
```
This becomes:
```css
@import 'tailwindcss';
@theme {
--color-gray-50: #fafafa;
--color-gray-100: #f5f5f5;
--color-gray-200: #e5e5e5;
--color-gray-300: #d4d4d4;
--color-gray-400: #a3a3a3;
--color-gray-500: #737373;
--color-gray-600: #525252;
--color-gray-700: #404040;
--color-gray-800: #262626;
--color-gray-900: #171717;
--color-gray-950: #0a0a0a;
}
```
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR builds on top of the new [JS config to CSS
migration](https://github.com/tailwindlabs/tailwindcss/pull/14651) and
extends it to support migrating _static_ plugins.
What are _static_ plugins you might ask? Static plugins are plugins
where we can statically determine that these are coming from a different
file (so there is nothing inside the JS config that creates them). An
example for this is this config file:
```js
import typographyPlugin from '@tailwindcss/typography'
import { type Config } from 'tailwindcss'
export default {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
darkMode: 'selector',
plugins: [typographyPlugin],
} satisfies Config
```
Here, the `plugins` array only has one element and it is a static import
from the `@tailwindcss/typography` module. In this PR we attempt to
parse the config file via Tree-sitter to extract the following
information from this file:
- What are the contents of the `plugins` array
- What are statically imported resources from the file
We then check if _all_ entries in the `plugins` array are either static
resources or _strings_ (something I saw working in some tests but I’m
not sure it still does). We migrate the JS config file to CSS if all
plugins are static and we can migrate them to CSS `@plugin` calls.
## Todo
This will need to be rebased after the updated tests in #14648
A few theme keys have changed in v4 relative to v3:
- `screens` -> `--breakpoint-*`
- `colors` -> `--color-*`
- `animation` -> `--animate-*`
- `borderRadius` -> `--radius-*`
- `boxShadow` -> `--shadow-*`
When using the `theme()` function we wouldn't pick up values from the
CSS for some of these. Likewise, when loading a v3 config not all of
these would be pushed back into the CSS theme and they should've been.
This PR addresses both of these problems.
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR injects a `@config "…"` in the CSS file if a JS based config has
been found.
We will try to inject the `@config` in a sensible place:
1. Above the very first `@theme`
2. If that doesn't work, below the last `@import`
3. If that doesn't work, at the top of the file (as a last resort)
When a stylesheet is imported with `@import “…” layer(utilities)` that
means that all classes in that stylesheet and any of its imported
stylesheets become candidates for `@utility` conversion.
Doing this correctly requires us to place `@utility` rules into separate
stylesheets (usually) and replicate the import tree without layers as
`@utility` MUST be root-level. If a file consists of only utilities we
won't create a separate file for it and instead place the `@utility`
rules in the same stylesheet.
Been doing a LOT of pairing with @RobinMalfait on this one but I think
this is finally ready to be looked at
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR attempts to detect simple postcss setups: These are setups that
do not load dynamic modules and are based off the examples we are
[recommending in our
docs](https://tailwindcss.com/docs/installation/using-postcss). We
detect wether a config is appropriate by having it use the object plugin
config and by not requiring any other modules:
```js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
```
When we find such a config file, we will go over it line-by-line and
attempt to:
- Upgrade `tailwindcss:` to `'@tailwindcss/postcss':`
- Remove `autoprefixer` if used
We then attempt to install and remove the respective npm packages based
on the package manger we detect.
And since we now have logic to upgrade packages, this also makes sure to
install `tailwindcss@next` at the end of a sucessful migration.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>