Commit graph

1075 commits

Author SHA1 Message Date
Robin Malfait
056a155072
4.3.2 (#20281) 2026-06-29 10:10:00 -04:00
Robin Malfait
c8b081d963
Add suggestions for named opacity modifiers (#20287)
This PR re-enables suggestions for named opacity modifiers that can be
configured via the `--opacity-*` namespace.

Before we launched v4, we got rid of this feature:
https://github.com/tailwindlabs/tailwindcss/pull/14278 &
https://github.com/tailwindlabs/tailwindcss/pull/14339
But later we re-added the feature, without updating intellisense
suggestions: https://github.com/tailwindlabs/tailwindcss/pull/15009

This PR re-adds suggestions for utilities that read from the
`--opacity-*` namespace for named modifiers. A lot of utilities use some
internal abstraction that get this for free once we use the
`colorUtility` setup, but some utilities don't use these, and I had to
add the `--opacity` modifier theme key manually.

Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1541

## Test plan

1. Update intellisense related tests now that we read from the
`--opacity-*` namespace
2. All tests pass

Before:
<img width="1122" height="1376"
alt="file-5462b830c5ebbd6046494713344ff5ff"
src="https://github.com/user-attachments/assets/ab0771c1-da8d-4179-b19d-183c2c567606"
/>


After:

<img width="1122" height="1376"
alt="file-9247e05165f72e8bb974673ee17bd35a"
src="https://github.com/user-attachments/assets/2afdd50f-6579-4a4c-bf9b-8d402fcf1184"
/>

Notice that the `/foo` suggestion is available
2026-06-29 15:35:45 +02:00
Robin Malfait
94f7907b55
add margin of error to the test
Sometimes this is flaky on CI, even though it doesn't make any sense
because we use `setTimeout(_, 500)` which should _at least_ result in
500ms...

But sometimes this results into:
```
AssertionError: expected 499.76 to be greater than or equal to 500
```
2026-06-25 19:23:36 +02:00
Robin Malfait
7ff413bad4
Ignore unknown at-rule warnings for @position-try (#20277)
This PR ignores Lightning CSS warnings for the unknow at-rule
`@position-try`.

This is a temporary fix/workaround until support for `@position-try`
lands in Lightning CSS:
https://github.com/parcel-bundler/lightningcss/pull/1238

Fixes: #20275

## Test plan

Ran a test based on the issue:

Before:
```shellsession
❯ tw -i x.css -o out.css --optimize
≈ tailwindcss v4.3.1

Found 1 warning while optimizing generated CSS:

│   position-try-fallbacks: --flip-above;
│ }
│ @position-try --flip-above {
┆              ^-- Unknown at rule: @position-try
┆
│   top: auto;
│   bottom: 0;

Done in 29ms
```

After:
```shellsession
❯ tw -i x.css -o out.css --optimize
≈ tailwindcss v4.3.1

Done in 14ms
```
2026-06-25 13:21:12 +02:00
Robin Malfait
bb6a10937c
use .ts instead of .css
In newer Vite versions this extension matters, without the
vite/resolvers integration tests won't work.

Since this is still a placeholder fake file where Vite will run
`dirname` on, the actual file doesn't really matter as long as it's
relative to this file.
2026-06-25 13:03:10 +02:00
depfu[bot]
c33b85d012
Update all pnpm dependencies (2026-06-19) (#20262)
This is your weekly update of **all** pnpm dependencies. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?

✳️ postcss-selector-parser (7.1.1 → 7.1.4, patch) ·
[Repo](https://github.com/postcss/postcss-selector-parser) ·
[Changelog](https://github.com/postcss/postcss-selector-parser/blob/master/CHANGELOG.md)
·
[Release](https://github.com/postcss/postcss-selector-parser/releases/tag/7.1.4)
·
[Diff](cf6637ed6a...4a7e4e3685)

✳️ semver (7.8.1 → 7.8.4, patch) ·
[Repo](https://github.com/npm/node-semver) ·
[Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md) ·
[Release](https://github.com/npm/node-semver/releases/tag/v7.8.4) ·
[Diff](76416081a8...8640bd68f1)

✳️ turbo (2.9.14 → 2.9.18, patch) ·
[Repo](https://github.com/vercel/turborepo) ·
[Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md) ·
[Release](https://github.com/vercel/turborepo/releases/tag/v2.9.18) ·
[Diff](fc62fe0d9c...3bdce3277d)




---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will only send you the next scheduled PR once
you merge or close this one.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu refresh</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-06-19 13:11:16 +02:00
Robin Malfait
c46f654fa0
Ensure --alpha(…) is seen as a color, and --spacing(…) is seen as a length (#20260)
This PR fixes an issue where intellisense recommends this
canonicalization:

```diff
- text-[calc(var(--spacing)*4)]
+ text-[--spacing(4)]
```

Which is correct, but the issue is that the result is different due to
ambiguity of the `text-*` utilities.

```css
.text-\[calc\(var\(--spacing\)\*4\)\] {
  font-size: calc(var(--spacing) * 4);
}
.text-\[--spacing\(4\)\] {
  color: calc(var(--spacing, 0.25rem) * 4);
}
```

Notice that we're using `color` all of a sudden? This is because that's
the default and we infer the data type based on the arbitrary value. The
`calc(…)` infers that the type is `length`, but we don't know what the
type of `--spacing(…)` is so we fallback to the default type which would
be `color`.

This PR makes sure that built in functions like `--alpha(…)` and
`--spacing(…)` are resolved as `color` and `length` respectively.

With this fix in place, this is the result:
```css
.text-\[calc\(var\(--spacing\)\*4\)\] {
  font-size: calc(var(--spacing) * 4);
}
.text-\[--spacing\(4\)\] {
  font-size: calc(var(--spacing, 0.25rem) * 4);
}
```


Fixes: #20256
Fixes: #20258

## Test plan

1. Added a regression test to make sure this doesn't happen anymore
2026-06-18 15:02:18 +02:00
Robin Malfait
5e9f66e428
Ensure @variant can be used in JS based APIs (#20252)
This PR doesn't fix any issues, but it does add an integration test (as
a regression test) to make sure that `@variant` with default variants
and custom variants can be used inside of JS based plugin APIs.

In Tailwind CSS v4.3.1 we introduced a PR that handles `@variant` in the
`addBase` Plugin API
(https://github.com/tailwindlabs/tailwindcss/pull/19480). This was a bit
of an older PR, but the tests made sense, so it was merged.

However, by introducing that PR, we introduced a bug that the `@variant`
was handled too early. If you added custom variants later _and_ used it
in the `addBase`, then you would get an error since the variant isn't
available (yet).

That issue was fixed by
https://github.com/tailwindlabs/tailwindcss/pull/20247

Now the question remains, why did we even have the original PR when it
already worked?

The use case we had was using `@variant` as part of the
`@tailwindcss/typography` plugin configuration for one of our templates.
I was indeed able to reproduce the issue where `@variant lg` was seen in
the output CSS file.

Turns out that this template was using `@tailwindcss/typography` +
`@variant` in the configuration, but it was also using Tailwind CSS
v4.1.15.

Upgrading to the latest version automagically fixed the issue we had.
This is also the behavior you can see in the integration test.

The correct behavior was introduced in an even older PR
https://github.com/tailwindlabs/tailwindcss/pull/19263

All that said, everything should work in the next release related to
`@variant` usages inside JS based APIs.


**Tiny improvement**

While debugging what's going on, I noticed that we looped over the AST
to get some nodes out and we did that twice. This PR also improves that
by re-using the same list of nodes instead of computing it twice. This
won't have a huge impact, but it happened while compiling every single
utility which is not ideal.

## Test plan

1. All tests should pass
2. I can't see `@variant` in the output CSS file

Input:
<img width="655" height="323" alt="image"
src="https://github.com/user-attachments/assets/20c8d524-3575-488c-b0c0-5c4669f37dc7"
/>

Before:
<img width="477" height="175" alt="image"
src="https://github.com/user-attachments/assets/045e2086-1d4b-487c-96ff-676351412935"
/>


After:
<img width="484" height="175" alt="image"
src="https://github.com/user-attachments/assets/24d950b1-7c28-40f8-96bc-81b38be0e7a3"
/>
2026-06-17 15:23:20 +02:00
Robin Malfait
707c23b955
Ensure custom variants can be used via @variant in addBase (#20247)
This PR fixes an issue where `@variant` inside `addBase` is being used
with a custom variant.

The issue is that we substitute the `@variant` calls immediately when we
call the `addBase` function. That means that variants that aren't
processed yet will error out.

This is a regression, because this used to work in Tailwind CSS v4.3.0
and started failing in Tailwind CSS v4.3.1.

## Test plan

1. Added a regression test
2026-06-16 23:05:50 +02:00
Robin Malfait
cc3b634fe0
Ensure @tailwindcss/cli watches the input file (#20246)
This PR fixes an issue where if the `--input` file (when using
`@tailwindcss/cli`) lives in an ignored folder, then changes to the
input file won't trigger a rebuild.

This happened in #17632 where the input file lives in the `assets/`
folder which is ignored by default.

However, if you make a change to the input file, then the CLI doesn't
restart which means that you can't update the `@source` directives
without quitting the program and re-starting it manually.

This PR solves that by automatically injecting an `@source` with the
path to the input file. This way the CLI will see changes, even if the
input file is in an ignored directory.

Fixes: #17632

## Test plan

1. Added an integration test
2. Existing tests pass
3. Reproduced it on the 


Before:
<img width="1122" height="1376"
alt="file-295ce4696b6b3199a3915c63f8bb50cd"
src="https://github.com/user-attachments/assets/09e5e965-38d1-474d-bfc6-3b5e9bca73b6"
/>

After:
<img width="1122" height="1376"
alt="file-301b2f2e06aee045957355014ae5de87"
src="https://github.com/user-attachments/assets/2b6d4cba-f456-4138-98bd-2259876fe70a"
/>
2026-06-16 18:38:29 +02:00
Robin Malfait
51f0760d29
Fix crash when using @tailwindcss/vite using deno v2.8.x (#20245)
This PR might fix an issue where resolvers don't work on deno v2.8.x
(they do work on deno v2.7.x) when using `@tailwindcss/vite`.

This happens when the `context.parentURL` is not actually a URL at all,
and therefore the `new URL(…)` just crashes. This PR will fallback to
the incoming result for files that are not passed proper URLs.

Fixes: #20232

## Test plan

1. Existing tests pass
2. Can't seem to figure out how to test this in the reproduction issue
without publishing first. So going to merge this and test the insiders
version instead.
2026-06-16 16:03:27 +02:00
Robin Malfait
127d17033a
Add bare value support for auto-rows-* and auto-cols-* (#20229)
This PR adds bare value support for `auto-rows-*` and `auto-cols-*`.

We first introduced `auto-rows-auto`, `auto-rows-min`, `auto-rows-max`
and `auto-rows-fr` (same for `auto-cols-*`) back in Tailwind CSS v1.9
(https://v1.tailwindcss.com/docs/grid-auto-rows#app) but we haven't
touched it since.

This PR now adds support for bare values that use the spacing scale.
That means that you can now use `auto-rows-12` or `auto-cols-12` which
will result in the following CSS:

```css
.auto-cols-12 {
  grid-auto-columns: calc(var(--spacing) * 12);
}
.auto-rows-12 {
  grid-auto-rows: calc(var(--spacing) * 12);
}
```

We could also add support for percentage based values. The only question
is what the syntax should be. This can either be `auto-rows-3/4` or
`auto-rows-75%`.

For the fraction case, we already have `w-3/4` and `aspect-3/4` even
though they both have a different value as a result:
```css
.aspect-3\/4 {
  aspect-ratio: 3/4;
}
.w-3\/4 {
  width: calc(3 / 4 * 100%);
}
```

We also have some precedence for the `%` value as well, e.g. `via-10%`
for gradient color stops.

I think I would personally towards the `auto-rows-75%` value instead of
`auto-rows-3/4`. The fraction syntax works great for aspect ratio
because that's literally what it is (`aspect-16/9`). The fraction also
works great for widths, because you typically have 2 elements next to
each other:
```html
<div>
  <div class="w-1/3"></div>
  <div class="w-2/3"></div>
</div>
```

Since you only use the `auto-rows-*` once on a parent element, I think
the `auto-rows-75%` makes a bit more sense than `auto-rows-3/4` since
there is no _other_ element (at least not that I can think of).


## TODO

- [ ] Support percentages?
   - [ ] Syntax A: `auto-rows-3/4`
   - [ ] Syntax B: `auto-rows-75%`
   - [ ] Syntax C: support both, but that seems silly

Percentage support isn't a blocker for this PR, since you can always use
`auto-rows-[75%]` if you want

## Test plan

1. Added a test to prove that this works
1. Existing tests still pass

Requested by:
https://github.com/tailwindlabs/tailwindcss/discussions/20225
2026-06-16 11:50:35 +02:00
Robin Malfait
abc4670a9a
Prevent crash when using @tailwindcss/cli using --watch on Windows (#20242)
This PR fixes an issue on Windows where the `@tailwindcss/cli` with the
`--watch` flag crashes when using a `@source` with a base path that
doesn't exist on disk.

This happens when setting up the `@parce/watcher` for directories that
don't exist. This PR essentially filters out these directories that
don't exist on disk to prevent the crash.

It might be that if you add the folder _later_, while the watcher is
already watching, that you have to restart the `@tailwindcss/cli` (or
save the `index.css` (the file that contains the `@source` directives),
this also recreates watchers from scratch).

If this issue causes problems for `@tailwindcss/postcss` and
`@tailwindcss/vite` in the future as well, then we can move this logic
back to Oxide. We do maintain the incoming `@source` files as best as
possible without resolving to absolute paths. Back when we did resolve
them, if that process error'd we just never returned the glob.

The root cause is still referencing folders that don't exist.

Fixes: #20231

## Test plan

1. Added a failing test, that did fail on Windows
2. Once fixed, all tests should pass

[ci-all]

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-06-15 17:20:31 +02:00
depfu[bot]
d1f9f53bf9
Update all pnpm dependencies (2026-06-15) (#20241)
This is your weekly update of **all** pnpm dependencies. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?

✳️ @emnapi/core (1.10.0 → 1.11.1, minor) ·
[Repo](https://github.com/toyobayashi/emnapi) ·
[Release](https://github.com/toyobayashi/emnapi/releases/tag/v1.11.1) ·
[Diff](ba84999164...6c3668c9af)

✳️ @emnapi/runtime (1.10.0 → 1.11.1, minor) ·
[Repo](https://github.com/toyobayashi/emnapi) ·
[Release](https://github.com/toyobayashi/emnapi/releases/tag/v1.11.1) ·
[Diff](ba84999164...6c3668c9af)

✳️ @tailwindcss/typography (0.5.19 → 0.5.20, minor) ·
[Repo](https://github.com/tailwindlabs/tailwindcss-typography) ·
[Changelog](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/CHANGELOG.md)
·
[Release](https://github.com/tailwindlabs/tailwindcss-typography/releases/tag/v0.5.20)
·
[Diff](e002ab89ad...e3714a3fe5)

✳️ @emnapi/wasi-threads (1.2.1 → 1.2.2, patch) ·
[Repo](https://github.com/toyobayashi/emnapi)




---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will only send you the next scheduled PR once
you merge or close this one.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu refresh</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
</dl></blockquote>
</details>

---------

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-06-15 13:13:32 +00:00
Robin Malfait
8a14a71010
4.3.1 (#20226) 2026-06-12 19:33:35 +02:00
Truffle
522288ca08
Serve ESM type declarations to ESM importers of @tailwindcss/postcss (#20228)
## Summary

Fixes #20219.

The `@tailwindcss/postcss` exports map serves the CJS-shaped
`dist/index.d.ts` (`export = _default`) for both the `import` and
`require` conditions, while the correct ESM declaration file
`dist/index.d.mts` (`export { _default as default }`) is built and
published but never referenced. Type checkers that treat the import
condition as ESM reject the default import with TS1192 — concretely,
`deno check` on Deno 2.8.3+ (which ships TypeScript 6.0) fails on
`import tailwindcss from "@tailwindcss/postcss"`.

This splits the export entry into per-condition `types` blocks so ESM
importers resolve `index.d.mts` and CJS consumers keep `index.d.ts` —
the same shape `@tailwindcss/vite` already uses (`"types":
"./dist/index.d.mts"`).

Two notes on the issue as filed:

- Stock `tsc` with NodeNext does **not** reproduce the error
(declaration file format follows the file extension and package `type`,
not the matched condition), so I didn't take the suggested `export =` →
`export default` change in `index.d.ts` — that would break CJS
consumers, and the correct ESM declarations already ship.
- `@tailwindcss/node` has the same single-`types` exports shape; happy
to follow up there if you want parity.

## Test plan

Against `@tailwindcss/postcss@4.3.0` with the published exports map,
then again with only this `package.json` change applied to the installed
package:

```sh
# Deno 2.8.3 (TypeScript 6.0), nodeModulesDir: auto
deno check main.ts   # before: TS1192 "Module ... index.d.ts has no default export" — after: passes
```

```sh
# typescript@6.0.0-dev (NodeNext), one ESM importer + one .cts require importer
tsc --noEmit         # passes both before and after (no regression for npm consumers)
```

`@arethetypeswrong/cli`:

| | published 4.3.0 | with this change |
|---|---|---|
| node16 (from ESM) | 🎭 Masquerading as CJS | 🟢 (ESM) |
| node16 (from CJS) | 🟢 (CJS) | 🟢 (CJS) |
| bundler | 🟢 | 🟢 |
2026-06-12 14:33:16 +00:00
Robin Malfait
12833aa4b3
Fix canonicalization bug where we end up with a high precision number (#20221)
This PR fixes a bug where we suggest a canonicalization with a high
precision number.

E.g.:

`w-[calc(100%/3.5)]` → `w-[28.571428571428573%]`

While this is technically correct, it's also not as user friendly. This
PR solves this issue by checking whether the result has a precision of
`.xx` at most. If that's not the case, then we keep the original
expression.

Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1591

## Test plan

1. Added an a regression test for this situation
2. Existing tests pass
2026-06-12 00:23:11 +02:00
Rohan Santhosh Kumar
97a5b3abfb
docs: fix double word 'to to' in test comment (#20216)
## Summary

Fixes a double word typo in a test comment: 'No need to to wrap' → 'No
need to wrap'.

## Test plan

This is a comment-only change in a test file. No functional changes.

Co-authored-by: Codex <codex@openai.com>
2026-06-11 12:07:00 +02:00
Robin Malfait
d01e103cc4
Add missing inset keyword for inset-shadow-none (#20208)
This PR fixes an issue where the `inset-shadow-none` class did not use
the `inset` keyword. This PR fixes that by introducing the `inset`
keyword:

```diff
  .inset-shadow-none {
-   --tw-inset-shadow: 0 0 #0000;
+   --tw-inset-shadow: inset 0 0 #0000;
    box-shadow: var(--tw-inset-shadow), var
  }
```

While the end effect is the same (there will be no shadow), it also
means that we switch between the types of shadows. This results in the
fact that things like transitions don't behave the way they should.

A minimal reproduction looks like this:
https://play.tailwindcss.com/iOrnKWP49a (depending on when you visit
this link, it might be fixed)

## Test plan

1. Updated tests to reflect
2. Verified in the Vite playground that this is the case. In the video
you will notice that the transition is smooth in the fixed case, but
there is no transition in the current state:



https://github.com/user-attachments/assets/15d8c78e-6be5-4c6c-8df9-1044c14e8f39
2026-06-08 23:08:52 +02:00
Robin Malfait
68fcdf1f05
Do not crash when migrating empty classes (#20205)
This PR fixes a crash while using `npx @tailwindcss/upgrade` when
migrating classes with no body inside of an `@layer utilities`.

When running `npx @tailwindcss/upgrade`, one thing we do is migrate the
CSS from:
```css
@layer utilities {
  .foo {
    color: red;
  }
}
```
To:
```css
@utility foo {
  color: red;
}
```

We already have some logic that leaves non-classe (IDs, attribute
selectors, ...) alone. But if we are migrating a class that has no body,
then we will migrate that as well:
```css
@layer utilities {
  .empty {
  }
}
```
Is turned into:
```css
@utility empty {
}
```

But later in the migration process this will result in an error because
a `@utility` has to have at least _some_ nodes.

Ideally, you don't even have CSS that has empty rules since it doesn't
have any effect in the browser (except of making your CSS file bigger),
but it could be that you don't have control over this file, so a fix is
still valid.

This PR solves that by leaving those rules alone, and keep them in an
`@layer utilities`.

Fixes: #20204

## Test plan

1. Added a dedicated test for this usecase
2026-06-07 21:50:35 +02:00
Jordan Pittman
ad6693906a
Allow @variant to be used inside addBase (#19480)
This seems like an unintentional omission

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-06-04 10:46:40 +00:00
Aarni Koskela
efae52c3af
Simplify CSS when using utilities that use a *-0 or *-1 value (#20196)
## Why?


While inspecting a Tailwind-powered site, I noticed selectors like

```css
.inset-0{inset:calc(var(--spacing) * 0)}
.inset-x-0{inset-inline:calc(var(--spacing) * 0)}
.top-0{top:calc(var(--spacing) * 0)}
```

which seem a bit silly, not to mention more complex for the end-user
device parsing the CSS.

## Summary

This PR adjusts helpers to not generate `calc(... * 0)` expressions.

https://github.com/tailwindlabs/tailwindcss/pull/19095 does a similar
thing on CSS AST, but that doesn't run during the build proper.

## Test plan

Ran `pnpm build && pnpm test && pnpm test:integration`. (Some unrelated
tests failed on my machine, hopefully less in CI.)

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-06-03 16:44:09 +00:00
Robin Malfait
0c5f8bfd88
Ignore the standalone CLI (#20139)
This PR fixes an issue where if you use the standalone CLI, and you move
the standalone CLI into the current project, then we would scan that
standalone CLI as-if it contains Tailwind CSS classes. Since the CLI
contains actual Tailwind CSS classes, and is in fact readable text, this
binary would've been used as a source.

There are a few ways of fixing this, we could hardcode all the known
names, but that would result in an issue if you rename the CLI. We could
check whether it's a binary format and look for magic numbers at the
top. We could also check for a shebang at the top of the file and skip
it that way.

While some of these solutions might still be useful for the future. For
now I fixed it by essentially always ignoring `process.execPath`. That
way we never ever scan the actual executable regardless of whether you
renamed it or not.

Fixes: #20134

## Test plan

- Added an integration tests
- Works on every OS [ci-all]
2026-06-02 12:49:53 +02:00
Robin Malfait
44818a6cf6
Ensure @tailwindcss/cli recovers from a deleted transitive dependency (#20137)
This PR fixes an issue where the `@tailwindcss/cli` can get into a
non-recoverable state when any of the transitive dependencies break.

Tailwind CSS has 2 kinds of dependencies:

1. All your templates
2. All dependencies that contribute to your configuration such as the
`input.css`, any plugins, any `tailwind.config.js` files and so on.

When a template changes, we just have to scan for new Tailwind CSS
classes and emit a new CSS file. But when the `input.css` file, or any
of its dependencies changes, then we want to perform a full rebuild.

The idea is that your `@theme` might have changed, or new plugins have
been added, or old plugins have been removed.

If you have an `input.css` file:
```css
@import "tailwindcss";
@config "./tailwind.config.js";
```

That relies on a custom config: `tailwind.config.js`:
```js
const theme = require('./my-custom-theme.js');
module.exports = {
  theme
}
```

If that file relies on yet another file: `./my-custom-theme.js`, then
changes there should also trigger a full rebuild.

Since we're dealing with JavaScript here, we want to clear the require
cache and rebuild the dependency tree such that another change to any of
these files triggers a full fresh build.

However, if any of those (transitive) dependencies are deleted, then we
will end up in an invalid state. Creating a new compiler will result in
a build error. The compiler won't be able to figure out the entire
dependency tree, and we're stuck.

Once the user fixes the potentially missing dependency, the watchers
will not be watching any of those files because we created a fresh
compiler.

With this PR, we fix that by keeping track of old paths and using those
while we are still in an invalid state. The moment everything is fixed,
a fresh dependency tree is created and everything starts working again
without you having to restart the `@tailwindcss/cli` command.

Fixes: #20113
Closes: #20114
Closes: #20133

## Test plan

- Added an integration test that removes the transitive dependency.
Re-adding that file later will recover the CLI state.
2026-06-01 19:15:40 +02:00
Robin Malfait
6b43b6400a
Canonicalization: limit arbitrary to bare values conversion (#20130)
This PR improves the canonicalization process by limiting the bare
values to a certain amount.

Before this PR, whenever we have an arbitrary value, e.g. `left-[6px]`,
then we prefer to use a bare value instead e.g. `left-1.5`. In most
cases, this makes sense.

However, there are places where this doesn't really make sense
(https://x.com/kettanaito/status/2059987396050268589)

- `left-[99999px]` → `left-24999.75 `

The hard part is to figure out _why_ this feels wrong. The `.75` could
feel wrong, but in the `left-[6px]` → `left-1.5`, the `.5` makes sense.
If we reduce that big number to `left-[99996px]` → `left-24999`, then
there is no floating point but it still feels wrong.

One possibility I can think of is to analyze the incoming value and see
if we find certain patterns. All repeating numbers, fun numbers like
`1337`, common numbers most programmers know such as `720px`, `1280px`,
etc.

But instead of that, I think it's more reasonable to limit the bare
value such that the `px` based value doesn't exceed a big number. We can
improve the logic if there are other cases that don't really make sense.

The biggest value we have in our default theme is `--breakpoint-2xl:
96rem`, which is equivalent to `1536px`.

So I think any bare value that results in a value `<= 1536px` should
probably be fine.

In this case, `left-[99999px]` would stay as `left-[99999px]`, but
`left-[6px]` is still converted to `left-1.5`.

Note: this is only happening for arbitrary values being converted to
bare values _if_ they use the `--spacing` variable internally.

Values such as `z-[99999999999]` will still be converted to
`z-99999999999`, since the intent is still clear.

## Test plan

1. Added new tests for these limitations
2. Other existing tests still pass
2026-05-29 21:31:47 +02:00
Robin Malfait
d4f24c5f16
Fix invalid canonicalization where 0<unit> was migrated to 0 (#20127)
This PR fixes a bug in the canonicalization process when we simplify /
fold declarations that contain `0<unit>` values.

The reason we even try to fold these in the first place is to simplify
values such as `m-[0rem]` to `m-0`. The more values we can
fold/canonicalize, the better we can suggest replacements _if_ they are
the same.

One thing we know in CSS is that if you have a `<length>` type, and that
value is `0<unit>`, then we can safely change that to just `0`.
```css
width: 0rem;
width: 0;    /* `0` is a <length> */
```
However, if this was part of a `calc(…)` (or another CSS math function),
then this could make the calc expression invalid:
- `calc(1rem + 0px)` → `calc(1rem + 0)` — this goes from _valid_ to
_invalid_
At runtime the `1rem` can be converted to a `px` based valued, then
`16px + 0px` makes sense. Adding `0` without unit does not.
- `calc(1rem * 0px)` → `calc(1rem * 0)` — this goes from _invalid_ to
_valid_
At runtime the `1rem` can be converted to a `px` based value, but `16px
* 0px` would result in `0px^2` which doesn't make sense either.

We will still normalize values such as `-0.0rem` to just `0rem`, but not
`0` if we know it's unsafe to do so.

If we end up with top-level `calc(…)` expressions that can be folded,
then we will try to do that:
- `calc(0px * -1)` → `0`
- `calc(calc(0px * -1) + 1rem)` → `calc(0px + 1rem)`
Notice that the inner `calc(…)` was folded to `0px` not `0` because that
would make the `calc(0 + 1rem)` invalid.
Additionally, we could potentially fold the `calc(0px + 1rem)` to just
`1rem`, but we have to make sure that we don't introduce valid values
from invalid values `calc(0s + 1rem)` would be invalid, but folding it
to `1rem` would make it valid which is not good.

Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1579
2026-05-29 14:43:26 +02:00
Robin Malfait
829cdc930a
duration must be at least 500ms 2026-05-26 23:34:51 +02:00
Robin Malfait
2666194d5c
Improve internal instrumentation module (#20119)
This PR improves some of the internal instrumentation tooling we have.

While working on another branch, I updated the instrumentation tooling
to have a few different ways of measuring what's going on.

Until now, we had an `I.start(label)` and corresponding `I.end(label)`.
While this works, it also means that you have to make sure that you call
`I.end(label)` before every `return` to track things properly.

With this PR, I added a `I.span(label, () => /* some callback*/{})` API
that essentially does that in one go. It also handles promises and
resturns the value that was returned from the callback. This can be
useful in situations where you have a one-liner:
```ts
let css = I.span('toCss(…)', () => toCss(ast))
```

If your callback is longer, then you end up in a situation where you
have to indent your code, and if you want to stop measuring you have to
drop code in 2 places and re-indent:
```diff
- I.span('label', () => {
    …
- })
```

For this situation, I also added a `using _ = I.track(label)` API
instead. This can also be used in any block and automatically inserts
the `I.end(label)` on every exit point. This relies on the new `using`
keyword, but we already relied on that for the instrumentation module.

Last but not least, the constructor accepts a `shouldReport` which
defaults to the `env.DEBUG`. The reason for this change is so that it's
easier to report / not report during development instead of swapping out
an environment variable. Again, this is internal so there is no public
API change happening here.

## Test plan

All tests should still pass.
2026-05-26 23:20:39 +02:00
Robin Malfait
cdfae994ed
Remove unused test (#20117)
This PR cleans up some old stale test that has been skipped from the
beginning.

While this test wants to prove that migrating from Tailwind CSS v3 to
Tailwind CSS v4 can handle the `#{!important}` SCSS notation, it doesn't
prove that we can migrate an entire SCSS project. SCSS has much more
special syntax and we never supported that.

Let's get rid of this test that's doing nothing at the moment.
Especially since we don't want to migrate SCSS projects. Enabling this
might result in the false sense that we _do_ support SCSS migrations
which is not the case.

Closes: #20106
2026-05-26 19:45:00 +02:00
Robin Malfait
749c45ef26
Expose index and siblings on walk context (#20109)
This PR is a small improvement of the current `walk` implementation
where we will expose the `index` and the `siblings` on the current
context.

During a walk, we walk over objects that contain a `nodes: []` field.
The `ctx.parent` that already exists is a reference to the parent node,
but `ctx.siblings` is a reference to the `ctx.parent.nodes`.

The `ctx.index` is the index of the current node we are walking in the
`siblings` array. This way we can prevent the awkward
`ctx.parent?.nodes.indexOf(node)` which is a bit silly because we
already know the nodes we're walking and its index...

The `ctx.parent` can be `null`, but the `ctx.siblings` will never be
`null`, this can be seen in a situation like this:

```ts
let ast: AstNode = [nodeA, nodeB]

walk(ast, (node, ctx) => {
  if (node === nodeA) {
    ctx.parent === null; // Because there is no parent
    ctx.siblings === ast; // Because that's the current list we're looping over

    // Before this PR, we would have to do something like:
    let siblings = ctx.parent?.nodes ?? ast
  }
})
```

In the above example, the `ast` is a separately variable, but if this
was inlined, we would run into some issues:
```ts
walk([nodeA, nodeB], (node, ctx) => {
  if (node === nodeA) {
    ctx.parent === null; // Because there is no parent
    ctx.siblings === ast; // Because that's the current list we're looping over

    // At this point, there is no way to get to the `[nodeA, nodeB]` list
    // without moving it to a variable first.
  }
})
```

So, this PR doesn't change much, the additional information we track is
already known information that is now exposed to the caller of the
`walk` function.

In this PR we did update some usages and got rid of some awkward
`ctx.parent?.nodes ?? []` and `ctx.parent.nodes.indexOf(…)` usages.

## Test plan

- All tests still pass as expected
2026-05-24 22:32:05 +02:00
Jiahan Chen
982e920df8
Add Rspack optional peer dependency for @tailwindcss/webpack (#20027)
## Summary

This PR makes Rspack support explicit for `@tailwindcss/webpack` by
adding `@rspack/core` as an optional peer dependency alongside
`webpack`.

The loader already works through Rspack's webpack-compatible loader API,
as shown in this runnable example:
https://github.com/rstackjs/rstack-examples/tree/main/rspack/tailwindcss.
The README and package description are updated to document that usage.

## Test plan

No Rspack-specific tests were added. The loader implementation is
unchanged, and the existing webpack loader tests exercise the same
loader API path that Rspack uses, which should cover the relevant
behavior.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-23 11:14:48 +02:00
Robin Malfait
73983e1cf5
Fix 'Sourcemap is likely to be incorrect' warnings when using @tailwindcss/vite (#20103)
This PR fixes an issue where a bunch of warnings would be shown related
to sourcemaps.

This happens when we are dealing with CSS files that are _not_ Tailwind
CSS roots. In that case, in the `transform` step, we return the `src` of
that module as-is because we didn't modify anything. However, when
nothing changed, you have to return a `NullValue` such as `undefined`.

So this is a stupid little fix, but it should get rid of a bunch of
annoying warnings.

Fixes: #19930

## Test plan

- Added an integration test to mimic the problem
- Other tests still pass
- Tested it against the reproduction provided in #19930


Before:
<img width="1887" height="1763" alt="8oQNG5Lqr2B"
src="https://github.com/user-attachments/assets/2d8af456-4176-4f18-92a3-5327e395ac6b"
/>


After:
<img width="1885" height="1404" alt="8oQND5kWSb4"
src="https://github.com/user-attachments/assets/7dd98ea4-126b-45d4-9411-0afbacd2c797"
/>
2026-05-22 17:57:25 +02:00
Robin Malfait
e0a0c46c85
Cleanup lockfile / dependencies (#20102)
This PR reduces the installed dependencies by cleaning up the
`pnpm-lock.yaml` file.

This also pins `@parcel/watcher` such that the lockfile is generated
properly becauase of the patched dependencies.

This is a follow-up of #19499, but up to date with the latest state of
the repo.

## Test plan

- Lockfile is simpler. Most dependencies stayed the same, and were
published _months_ ago. There are a few cases where we have more recent
published dependencies. There are 7 dependencies that were published in
the last ~24 hours: `node-releases@2.0.46` (10 hours ago),
`electron-to-chromium@1.5.361` (12 hours ago), `semver@7.8.1` (20 hours
ago), `terser@5.48.0` (20 hours ago), `webpack-sources@3.5.0` (5 hours
ago), `vite@8.0.14` (yesterday). All of these but the `terser` version
used OIDC.
- Socket.dev didn't report any issues with the changed dependencies
- All tests still pass

---------

Co-authored-by: James Garbutt <43081j@users.noreply.github.com>
2026-05-22 16:57:46 +02:00
Jordan Brough
a480043a8a
Add --silent option to suppress output in @tailwindcss/cli (#20100)
Edited by: @RobinMalfait 

This PR adds a new `--silent` option to the `@tailwindcss/cli` to
suppress output (except for errors).

## Test plan

- Existing tests pass
- A new integration test for the `--silent` option was added

---

Original:

## Summary

Small change to add a `--quiet` option.

@adamwathan previously said this type of thing [sounded like a good
idea](https://github.com/tailwindlabs/tailwindcss/issues/8050#issuecomment-1100164351):

> [I] have made a note about the --silent option idea which I think
definitely has value 👍🏻

This is helpful to keep logs high signal in some scenarios. For example,
when I want AI to sift through my foreman logs, where "Done in X"
becomes the dominant log message over time when running tailwind
alongside other things.

## Test plan

I've added an integration test.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-22 13:03:32 +00:00
Robin Malfait
8dcdb66e8a
Bump dependencies (#20095)
This PR bumps some of our dependencies, common dependencies were moved
to pnpm's `catalog` feature.

Closes #20092
Closes #20085
Closes #20075
Closes #20066
Closes #20062


## Test plan

- All tests still pass
- Each dependency was published some time ago. Webpack has an even newer
version that was published <15min ago. Will update that one later.

[ci-all]
2026-05-21 17:58:55 +02:00
Robin Malfait
d03edefd19
Fix parsing bug in SelectorParser (#20090)
While working on another feature, I noticed that a selector such as
`.foo::before` was parsed as:

```ts
[
  {
    kind: 'compound',
    nodes: [
      { kind: 'selector', value: '.foo' },
      { kind: 'selector', value: ':' },
      { kind: 'selector', value: '::before' },
    ],
  },
]
```

Instead of:
```ts
[
  {
    kind: 'compound',
    nodes: [
      { kind: 'selector', value: '.foo' },
      { kind: 'selector', value: '::before' },
    ],
  },
]
```

So far this hasn't been a real issue in practice, but it is in a
follow-up PR that I'm working on. To keep things separated, I wanted to
fix this behavior in a dedicated PR instead.

## Test plan

1. Added a test case for this situation
2. Other tests still pass
2026-05-20 15:54:07 +02:00
Robin Malfait
2c4726d3d5
Simplify selector combinators (#20089)
This PR is a follow-up of #20088 to further improve selectors, in
particular the `combinator`.

This PR explicitly types the `combinator` as:
```ts
type Combinator =
  | ' ' // Descendant combinator
  | '>' // Child combinator
  | '+' // Next-sibling combinator
  | '~' // Subsequent-sibling combinator
```

This allows us to explicitly test for this pattern in various places,
without us having to call `.trim()` first to know what the actual
combinator was.

In the selector parser itself, we already did a `.trim()` to know
whether we were dealing with a descendant combinator or not. With this
PR, we further ensure that there is no whitespace involved aroudn these
combinators.

This introduces a small problem because we need to be able to re-print a
selector's AST. So if we don't track whitespace, we have to re-introduce
it. But there are situations where we don't want it at all (during
canonicalization).

To solve this, we introduced a `minify = false` option in the
`SelectorParser.toCss`. If it's false (the default), then we introduce
whitespace, otherwise we remove all whitespace.

## Test plan

1. All existing tests pass
2. Manual cleanup/trimming of descendants is no longer necessary

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-20 15:30:55 +02:00
Robin Malfait
fc17df05f2
Add list, compound, and complex Selector nodes (#20088)
This PR introduces a few more nodes in the `SelectorParser`:

- A `list` node
- A `complex` node
- A `compound` node

These names are closer to the CSS Selector AST names
(https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Selectors/Selector_structure),
and are also used in other libraries.

The problem today is that there are situations where we parse a selector
like: `#a.b > .c, .d` as:
```ts
[
  { kind: 'selector', value: '#a' },
  { kind: 'selector', value: '.b' },
  { kind: 'combinator', value: ' > ' },
  { kind: 'selector', value: '.c' },
  { kind: 'separator', value: ', ' },
  { kind: 'selector', value: '.d' }
]
```

Which is a very simple structure, but this contains a flaw that is
annoying to deal with in practice: In order to determine that we are
dealing with multiple selectors, we have to loop through the nodes and
see if a separator occurs somewhere.

The other fun thing is that we already know the difference between
selectors, combinators and separators. So if we tweak this structure a
little bit during parsing, then we can answer the question from above in
a much simpler way:

With this PR, we will parse the selector as:
```ts
[
  {
    kind: 'list',
    nodes: [
      {
        kind: 'complex',
        nodes: [
          {
            kind: 'compound',
            nodes: [
              { kind: 'selector', value: '#a' },
              { kind: 'selector', value: '.b' }
            ]
          },
          { kind: 'combinator', value: '>' },
          { kind: 'selector', value: '.c' }
        ]
      },
      { kind: 'selector', value: '.d' }
    ]
  }
]
```

It definitely looks more complex, but now that we have a `list` node, we
already know that we are dealing with multiple selectors.

If you squint your eyes, in the inner part there is a `compound`
selector. This is essentially a node where each sub-node can be squished
together with no spaces whatsoever.

The `complex` selector is there just to group everything together. In
other tools, a complex selector is often represented as:
```ts
{
  kind: 'complex',
  combinator: '>',
  lhs: { … },
  rhs: { … },
}
```
While I want to have the concept of a `complex` node, I didn't go with
this syntax just because I want to keep the concept of `nodes` which
means that we don't need any special handling when using `walk` (which
loops over `.nodes` internally).

The reason this complex node exists is because otherwise you would end
up with this structure:
```ts
[
  {
    kind: 'list',
    nodes: [
      {
        kind: 'compound',
        nodes: [
          { kind: 'selector', value: '#a' },
          { kind: 'selector', value: '.b' }
        ]
      },
      { kind: 'combinator', value: '>' },
      { kind: 'selector', value: '.c' }
      { kind: 'selector', value: '.d' }
    ]
  }
]
```
But if you look at the `list` node now, it's not clear that we are
dealing with `2` selectors since there are 4 nodes. We could solve this
by re-introducing the separator node (`,`). The fact that the `list`
exists tells us that we're dealing with `n` selectors. But to know which
selectors we're dealing with, then we have to look for that `,` node
again, which introduces the original problem.

This is just an internal refactor to make future changes easier.

## Test plan

1. Everything still works as expected (all tests pass)
2. No public API breaking changes, this parser was never exposed
2026-05-20 15:04:05 +02:00
Robin Malfait
cea8c9721b
Improve parsing of shadow values (#20080)
This PR fixes an issue where a `calc(…)` value used in a shadow value
was incorrectly marked as the color of that shadow.

We have this feature where we can have colored shadows, this requires us
to replace the color in a value with a `var(--tw-shadow-color,
<original-value-here>)` such that we can swap out the color.
For this, we have to parse the value and figure out what the color part.

This PR uses the `ValueParser` to parse values, then figure out what the
color part is. The biggest reason for this is that we know what a
"function" is and what a normal "word" is, which allows us to do more
fine-grained checks on a per-type basis.
We tracked a few more functions that we _know_ produce color values, and
a few cases that we know produce length values so therefore can't be the
color.

Some examples:

- `calc(…)`, `min(…)`, `max(…)`, `clamp(…)`, `--spacing(…)` — these all
produce length-values.
- `color(…)`, `color-mix(…)`, `rgba?(…)`, `--alpha(…)` … — these all
produce color values.

Notice that we added `--spacing(…)` and `--alpha(…)` as well, these are
custom functions that Tailwind CSS provides, but we know what it will
eventually map to internally.

Last but not least, we also detect named colors and hex-based colors.

Fixes: #20065 
Closes: #20074

## Test plan

1. Added an integration test, before the fix the test would've looked
lik this:

  ```diff
    .drop-shadow-calc {
- --tw-drop-shadow-size: drop-shadow(0 0 calc(1 * var(--spacing))
var(--tw-drop-shadow-color, black));
+ --tw-drop-shadow-size: drop-shadow(0 0 var(--tw-drop-shadow-color,
calc(1 * var(--spacing))) black);
      --tw-drop-shadow: drop-shadow(var(--drop-shadow-calc));
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, )
var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, )
var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
    }
  ```

2. Added more tests related to the shadow replacement logic itself where
we try to infer the color (or the length-values for x, y, blur, spread)
3. All other existing tests pass
2026-05-19 13:47:50 +02:00
Robin Malfait
36417cbd12
Properly negate custom variants with @container when used with not-* (#20059)
This PR fixes an issue where a custom variant declared with a
`@container` wasn't properly negated when using it in combination with
the `not-*` variant.

Given this CSS:
```css
@custom-variant has-a {
  @container style(--a) {
    @slot;
  }
}
```

If you then used `not-has-a:flex`, then the following CSS was produced:
```css
.not-has-a\:flex {
  @container style(--a) not {
    display: flex;
  }
}
```
But we expect the `not` to be in the correct location:
```css
.not-has-a\:flex {
  @container not style(--a) {
    display: flex;
  }
}
```

The issue was that we did some string related checks, and we assumed
that the `query` part of the `@container` (`style(--a)`) had to start
with a `(` character.

To fix this, we now parse the value to an AST, and verify the AST shape
before manipulating it. This now checks whether the `query` part is a
function (both `(…)` and `style(…)` are considered functions).

Also added some additional tests that were already handled, these cases
look like:

- `@container {query}`
- `@container not {query}`
- `@container {name} not {query}`
- `@container {name} {query}`


Fixes: #20058

## Test plan

1. Added a failing test for the use case of the linked issue.
2. Added a few more additional tests to explicitly track some use cases
we handled already but didn't track via tests.
3. All other tests still pass as expected.
2026-05-15 00:08:10 +02:00
Jordan Pittman
460a008120
v4: Allow @apply to be used with CSS mixins (#19427)
The CSS Custom Functions and Mixins spec [is using `@apply` with dashed
idents](https://drafts.csswg.org/css-mixins-1/#apply-rule) for native
mixin support. We shouldn't attempt to treat these as utilities and try
to compile them since they'll fail.

There one intentional limitation with regards to mixins and our use of
`@apply`: We do not allow users to mix utilities and mixins in the same
at-rule. In other words, all of the following `@apply` rules are
invalid:

```css
.foo {
  /* Invalid because the rules contain both mixins and utilities */
  @apply --my-mixin underline;
  @apply --my-mixin() underline;
  @apply underline --my-mixin;
  @apply underline --my-mixin();
}
```

Aside: Lightning CSS does not yet support this syntax so the results of
a production build won't produce the correct code but we'll at least
handle these correctly in Tailwind CSS itself.

Fixes #19422

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-14 14:22:26 +00:00
Serge Yudin
a2b89a7012
Fix canonicalize crash with plugin component values (#20052)
## Summary

Fixes #20051.

The collapse canonicalization pass speculatively checks compatible
functional utility roots. When one of those roots comes from a plugin
registered with `matchComponents`/`matchUtilities`, the speculative
candidate can call the plugin callback with an arbitrary value that is
not present in the configured `values` map. Plugins such as the Phoenix
Heroicons helper expect the mapped value shape and can throw while
canonicalize is only probing possible replacements.

This change skips speculative replacement utilities whose property
lookup throws, matching the best-effort behavior already used by utility
signature generation. The original candidates are preserved instead of
crashing canonicalization.

## Test plan

- `source ~/.nvm/nvm.sh && nvm use 22.14.0 && pnpm vitest run
packages/tailwindcss/src/canonicalize-candidates.test.ts -t "does not
crash when plugin matchComponents rejects speculative values during
collapse"`
- `source ~/.nvm/nvm.sh && nvm use 22.14.0 && pnpm vitest run
packages/tailwindcss/src/canonicalize-candidates.test.ts`
- `source ~/.nvm/nvm.sh && nvm use 22.14.0 && pnpm prettier --check
packages/tailwindcss/src/canonicalize-candidates.ts
packages/tailwindcss/src/canonicalize-candidates.test.ts`

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-14 09:59:19 +00:00
Robin Malfait
eefe64593a
Cleanup tests (#20053)
This PR does some cleanup work in the tests such that every test is
using the custom test helpers instead of creating the compiler itself
and using other test helpers such as `optimizeCss` or `pretty`.

For tests we have some helpers that introduce a small layer of
indirection. While you typically want to avoid indirection, this one
allows us to keep the tests the same whenever we make changes to the
internals.

It also allows us to run through the full core flow where we have to
setup a compiler, pass in the CSS, compile the candidates, optimize the
CSS and pretty print it for snapshot purposes.

Our tests often look like this when you are testing some candidates:
```ts
test('…', async () => {
  expect(await run(['flex', 'hover:flex'])).toMatchInlineSnapshot(`
    "
    .flex {
      display: flex;
    }

    @media (hover: hover) {
      .hover\\:flex:hover {
        display: flex;
      }
    }
    "
  `)
})
```

If you need to compile some CSS, we used the following:
```ts
test('…', async () => {
  expect(
    await compileCss(css`
      @tailwind utilities;
      .foo {
        @apply flex;
      }
    `),
  ).toMatchInlineSnapshot(`
    "
    .foo {
      display: flex;
    }
    "
  `)
})
```

With this PR, I normalized the tests by changing the signatures of those
tests slightly:
```ts
export async function run(
  // A list of candidates
  candidates: string[],

  // Optionally, custom CSS
  input = css`
    @tailwind utilities;
  `,

  // Optionally, custom compile options
  options: Parameters<typeof compile>[1] = {},
) {
  let { build } = await compile(input, options)
  return pretty(optimize(build(candidates)).code)
}

// Same as above, but without the candidates
export async function compileCss(css: string, options: Parameters<typeof compile>[1] = {}) {
  return run([], css, options)
}
```

They are very similar, but if we migrate _everything_ to `run`, then
there will be situations where you have to use:
```ts
test('…', async () => {
  expect(
    await run(
      [],
      css`
        @tailwind utilities;
        .foo {
          @apply flex;
        }
      `,
    ),
  ).toMatchInlineSnapshot(`
    "
    .foo {
      display: flex;
    }
    "
  `)
})
```

Which is a little bit confusing because what does `[]` even mean?

The next big change is that a lot of the tests were manually setting up
the compiler, passing in the CSS and compiler options, then building the
candidates, then manually optimizing the CSS and then manually pretty
printing the resulting CSS for snapshot purposes.

Other tests, didn't use all those steps and skipped the optimization
step for example. This means that not all tests were testing the
end-to-end core workflow.

This PR uses the tests helpers wherever we could. This now also means
that our tests look the same as much as possible.

The biggest goal of this was to get everything in a similar state.
Future PRs that add additional optimizations will now see those
optimizations reflected in the actual test output.

## Test plan

1. All tests still pass
- Some test _output_ has changed because the `optimizeCss` will now kick
in. But this reflects production better anyway.
2. No source code was changed
2026-05-14 11:43:34 +02:00
depfu[bot]
ece4824f0d
Update jiti 2.6.1 → 2.7.0 (minor) (#20044)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ jiti (2.6.1 → 2.7.0) · [Repo](https://github.com/unjs/jiti) ·
[Changelog](https://github.com/unjs/jiti/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/unjs/jiti/releases/tag/v2.7.0">2.7.0</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.6.1...v2.7.0">compare
changes</a></p>
<h3 dir="auto">🚀 Enhancements</h3>
<ul dir="auto">
<li>Add explicit resource management (<code
class="notranslate">using</code>/<code class="notranslate">await
using</code>) support (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/422">#422</a>)</li>
<li>Support opt-in <code class="notranslate">tsconfigPaths</code> (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/427">#427</a>)</li>
<li>Support virtual modules (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/428">#428</a>)</li>
<li>Add <code class="notranslate">jiti/static</code> subpath (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/430">#430</a>)</li>
</ul>
<h3 dir="auto">🔥 Performance</h3>
<ul dir="auto">
<li>
<strong>interopDefault:</strong> Add caching to reduce proxy overhead by
~2x (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/421">#421</a>)</li>
</ul>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>
<strong>require:</strong> Passthrough resolve options (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/412">#412</a>)</li>
<li>
<strong>require:</strong> Fallback to transpilation when <code
class="notranslate">tryNative</code> fails (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/413">#413</a>)</li>
<li>Fallback for <code class="notranslate">ENAMETOOLONG</code> when
evaluating esm (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/429">#429</a>)</li>
</ul>
<h3 dir="auto">📦 Build</h3>
<ul dir="auto">
<li>Upgrade rspack to v2 (<a
href="55194fb">55194fb</a>)</li>
<li>Experimental rolldown config (<a
href="8c0243f">8c0243f</a>)</li>
</ul>
<h3 dir="auto">✅ Tests</h3>
<ul dir="auto">
<li>Ignore jsx test for bun/cjs (<a
href="3a744ca">3a744ca</a>)</li>
</ul>
<h3 dir="auto">❤️ Contributors</h3>
<ul dir="auto">
<li>Pooya Parsa (<a
href="https://bounce.depfu.com/github.com/pi0">@pi0</a>)</li>
<li>Kricsleo (<a
href="https://bounce.depfu.com/github.com/kricsleo">@kricsleo</a>)</li>
<li>Espen Hovlandsdal (<a
href="https://bounce.depfu.com/github.com/rexxars">@rexxars</a>)</li>
<li>Rintaro Itokawa (<a
href="https://bounce.depfu.com/github.com/re-taro">@re-taro</a>)</li>
<li>Matteo Collina (<a
href="https://bounce.depfu.com/github.com/mcollina">@mcollina</a>)</li>
<li>Mario Zechner (<a
href="https://bounce.depfu.com/github.com/badlogic">@badlogic</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="aedcdee7fe...fd3bb289b7">See
the full diff on Github</a>. The new version differs by 29 commits:</p>
<ul>
<li><a
href="fd3bb289b7"><code>chore(release):
v2.7.0</code></a></li>
<li><a
href="27fe3f2a49"><code>chore:
update release script</code></a></li>
<li><a
href="4fcd2f23aa"><code>fix:
fallback for `ENAMETOOLONG` when evaluating esm (#429)</code></a></li>
<li><a
href="8c0243f14e"><code>build:
experimental rolldown config</code></a></li>
<li><a
href="55194fbb97"><code>build:
upgrade rspack</code></a></li>
<li><a
href="0abda72c11"><code>ci:
update node test matrix</code></a></li>
<li><a
href="8c7822ef2f"><code>chore:
update tsconfig</code></a></li>
<li><a
href="08fc868c92"><code>chore:
update deps</code></a></li>
<li><a
href="5d552e3beb"><code>feat:
add `jiti/static` export (#430)</code></a></li>
<li><a
href="ae790b0214"><code>feat:
support virtual modules option (#428)</code></a></li>
<li><a
href="a3e705dbdc"><code>fix(require):
fallback to transpilation when `tryNative` fails (#413)</code></a></li>
<li><a
href="4deba16283"><code>chore:
update agents.md</code></a></li>
<li><a
href="f85b0e523d"><code>lint</code></a></li>
<li><a
href="3ce242653f"><code>feat:
support opt-in `tsconfigPaths` (#427)</code></a></li>
<li><a
href="c49c54e42e"><code>chore:
init agents.md</code></a></li>
<li><a
href="b66bd233c2"><code>feat:
add explicit resource management (using/await using) support
(#422)</code></a></li>
<li><a
href="a467d31ffc"><code>perf(interopDefault):
add caching to reduce proxy overhead by ~2x (#421)</code></a></li>
<li><a
href="fe264b49f1"><code>fix(ci):
skip `--coverage` flag for node 18</code></a></li>
<li><a
href="058d91a338"><code>chore:
lint</code></a></li>
<li><a
href="650bc48a76"><code>chore:
add missing prettier dep</code></a></li>
<li><a
href="e28b5e9645"><code>chore(deps):
update autofix-ci/action digest to 7a166d7 (#424)</code></a></li>
<li><a
href="31096aa9e2"><code>chore(deps):
update actions/checkout action to v6 (#419)</code></a></li>
<li><a
href="498e8d73a4"><code>chore:
update deps</code></a></li>
<li><a
href="9ee314fd0f"><code>test:
update</code></a></li>
<li><a
href="3a744ca271"><code>test:
ignore jsx test for bun/cjs</code></a></li>
<li><a
href="e88ac44079"><code>chore:
update deps</code></a></li>
<li><a
href="4045c7a2a2"><code>chore:
fix lint issues</code></a></li>
<li><a
href="39e86de57c"><code>chore(deps):
update actions/setup-node action to v6 (#411)</code></a></li>
<li><a
href="ddb4683d37"><code>fix(require):
passthrough resolve options (#412)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-05-13 12:02:13 +02:00
Robin Malfait
54d6b05b44
move localRequire to else branch 2026-05-12 11:19:48 +02:00
Antoine Lethimonnier
fc432e0fd5
fix(node): prefer Module#registerHooks over Module#register (#20028)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

Fix this warning when building apps with TailwindCSS with Node 26+:
```
(node:25346) [DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.
    at node:internal/util:129:11
    at Module.register (node:internal/modules/esm/loader:969:3)
    at file:///Users/antoine/Developer/my-app/node_modules/.pnpm/@tailwindcss+node@4.3.0/node_modules/@tailwindcss/node/dist/index.mjs:18:214
    at ...
```

This PR:
- Correctly prefers using `Module#registerHooks` instead of
`Module#register` by checking its availability at runtime
- Adjusts the exports of the hooks’ file by creating a synchronous
version for the new API
- Remove now unused exports from the `package.json`, relying on the
[recommended usage in the
docs](https://nodejs.org/docs/latest/api/module.html#registration-of-asynchronous-customization-hooks)
for the `Module#register` calls

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

I'd be happy to ensure this works correctly on my end before merging
this, but it's not as trivial to test locally as a logic fix. Can you
guide me through what I need to do to test this?

I extensively based my change on the docs, following those guides:
- [Fixing imports for
`Module#register`](https://nodejs.org/docs/latest/api/module.html#registration-of-asynchronous-customization-hooks)
- [Using the new `Module#registerHooks`
function](https://nodejs.org/docs/latest/api/module.html#registration-of-synchronous-customization-hooks)

(and other more detailed sections of the same docs page)

---

Fixes #19893
Closes #19907

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-11 14:25:28 +00:00
Robin Malfait
588bd7371f
4.3.0 (#20023) 2026-05-08 22:03:59 +02:00
Robin Malfait
59936c6cbb
Add tab-* utilities (#20022)
This PR adds new `tab-*` utilities.

The `tab-*` utilities set the `tab-size` property. They support positive
integer bare values, and arbitrary values:

| Class | CSS |
| -- | -- |
| `tab-2` | `tab-size: 2;` |
| `tab-[12px]` | `tab-size: 12px;` |

This also adds `tab-size` to the property order near the other text
layout properties.

## Test plan

1. Added new `tab-*` utility tests
2. Updated the intellisense snapshot
3. Other existing tests still pass
2026-05-08 21:22:07 +02:00
Robin Malfait
90a2373620
add zoom-* utilities (#20020)
This PR adds new `zoom-*` utilities.

The `zoom-*` utilities accept bare values that are transformed into `%`
based values, and arbitrary values:

| Class | CSS |
| -- | -- |
| `zoom-50` | `zoom: 50%;` |
| `zoom-[1.1]` | `zoom: 1.1;` |
| `zoom-(--value)` | `zoom: var(--value);` |


This also adds the `zoom` property after the `transform` related
properties. Initially I added it right after `scale`, because logically
they are close together. But then `zoom-*` would sit between `scale` and
other tranfsorm related properties which is a bit weird. Instead, I
moved it after `transform`.

## Test plan

1. Added new zoom based tests
2. All other tests still pass
2026-05-08 21:15:59 +02:00
Robin Malfait
2e1ccf7f11
Add scrollbar-gutter-* utilities (#20018)
This PR adds new `scrollbar-gutter-*` utilities. The API looks like
this:

| Class | CSS |
| -- | -- |
| `scrollbar-gutter-auto` | `scrollbar-gutter: auto` |
| `scrollbar-gutter-stable` | `scrollbar-gutter: stable` |
| `scrollbar-gutter-both` | `scrollbar-gutter: stable both-edges` |
2026-05-08 21:13:06 +02:00