Commit graph

1503 commits

Author SHA1 Message Date
Robin Malfait
d0a1bd655b
Show Lightning CSS warnings when optimizing/minifying in production (#18918)
This PR improves the DX by showing all the Lightning CSS warnings when
using a "production" build (or using `--optimize` or `--minify` flags
when using the CLI).

Right now Tailwind CSS itself doesn't care about the exact syntax you
are using in the CSS as long as it looks valid. We do this because
otherwise we would have to parse a lot more CSS syntax and validate it
even though it would be valid CSS in 99.99% of the cases.

Even worse, if you want to use newer CSS syntax that Tailwind CSS
doesn't validate yet, then you would get warnings for valid CSS.

Another reason why we don't do this is because the browser already does
a great job at ignoring invalid CSS.

So the linked issue #15872 would still silently fail in development
mode. In this case, everything would work, except the shadow with the
invalid syntax.

But in production mode, you would now get a proper warning from
Lightning CSS, because they try to optimize the CSS and remove invalid
CSS.

One potential issue here is that we run Lightning CSS on the generated
CSS, not on the input CSS. So the current output shows the warnings in
the output CSS not the input CSS. Any thoughts if we would just skip the
line numbers?

## Test plan

1. Everything works as before
2. In production mode, you would get warnings printed to the terminal.
This is done in `@tailwindcss/node` so the CLI/Vite/PostCSS plugins
would all get the same behavior.

Screenshots:

If you have a single issue:
<img width="977" height="441" alt="image"
src="https://github.com/user-attachments/assets/7b061ee9-b74f-4b40-aa05-cff67a21dfcc"
/>


If you have multiple issues:
<img width="2170" height="711" alt="image"
src="https://github.com/user-attachments/assets/a5bc9b0a-964b-465f-80f3-d30dd467e69c"
/>


Fixes: #15872
2025-09-12 12:27:23 +02:00
Robin Malfait
65bad11380
Do not migrate variant = 'outline' during upgrades (#18922)
This PR improves the upgrade tool for shadcn/ui projects where the
`variant = "outline"` is incorrectly migrated to `variant =
"outline-solid"`.

This PR also handles a few more cases:

```ts
// As default argument
function Button({ variant = "outline", ...props }: ButtonProps) { }

// With different kinds of quotes (single, double, backticks)
function Button({ variant = 'outline', ...props }: ButtonProps) { }

// Regardless of whitespace
function Button({ variant="outline", ...props }: ButtonProps) { }

// In JSX
<Button variant="outline" />

// With different quotes and using JavaScript expressions
<Button variant={'outline'} />

// As an object property
buttonVariants({ variant: "outline" })
```
2025-09-12 09:20:18 +00:00
Philipp Spiess
d1fd645beb
Proposal: Allow overwriting static utilities that have a namespace (#18056)
This PR attempts to move static utilities that are overwriteable by a
theme value to be a fallback rather than a conflicting implementation.
The idea is to allow a theme value to take presedence over that static
utility _and cause it not to generate_.

For example, when overwriting the `--radius-full` variant, it should
ensure that the default `rounded-full` no longer emits the
`calc(infinity * 1px)` declaration:

```ts
expect(
  await compileCss(
    css`
      @theme {
        --radius-full: 99999px;
      }
      @tailwind utilities;
    `,
    ['rounded-full'],
  ),
).toMatchInlineSnapshot(`
  ":root, :host {
    --radius-full: 99999px;
  }

  .rounded-full {
    border-radius: var(--radius-full);
  }"
`)
```

This allows anyone who wants `--radius-full` to be a CSS variable to
simply define it in their theme:

```css
@theme {
  /* Make `--radius-full` a CSS variable without the utility generating two CSS classes */
  --radius-full: calc(infinity * 1px);
}
```

The idea is to extend this pattern across all functional utilities that
also have static utilities that can collide with the namespace. This
gives users more control over what they want as CSS variables when the
defaults don't work for them, allowing them to resolve #16639 and #15115
in user space.

You may now find yourself thinking "but Philipp, why would someone want
to be able to overwrite `--animate-none`. `none` surely always will mean
no animation" and I would agree [but it's already possible right now
anyways so this is not a new behavior! This PR just cleans up the
generated output.](https://play.tailwindcss.com/StnQqm4V2e)

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-09-11 12:21:50 +02:00
Robin Malfait
340b59dcde
Do not generate grid-column when configuring grid-column-start or grid-column-end (#18907)
This PR fixes an issue where configuring a custom `--grid-column-start`
or
`--grid-column-end` also generated a `grid-column` utility due to the
overlapping namespace.

```css
@theme {
  --grid-column-start-custom: custom-start;
  --grid-column-end-custom: custom-end;
}
```

Would then generate:
```css
.col-end-custom {
  grid-column: var(--grid-column-end-custom);
}
.col-start-custom {
  grid-column: var(--grid-column-start-custom);
}
.col-start-custom {
  grid-column-start: var(--grid-column-start-custom);
}
.col-end-custom {
  grid-column-end: var(--grid-column-end-custom);
}
```

Instead of the expected:
```css
.col-start-custom {
  grid-column-start: var(--grid-column-start-custom);
}
.col-end-custom {
  grid-column-end: var(--grid-column-end-custom);
}
```

Fixes: #18906
2025-09-10 15:57:29 +02:00
Philipp Spiess
ee1c7a69dc
Fix CLI watcher cleanup race (#18905)
This PR supersets #18559 and fixes the same issue reported by @Gazler.

Upon testing, we noticed that it's possible that two parallel
invocations of file system change events could cause some cleanup
functions to get swallowed.

This happens because we only remember one global cleanup function but it
is possible timing wise that two calls to `createWatcher()` are created
before the old watchers are cleaned and thus only one of the new cleanup
functions get retained.

To fix this, this PR changes `cleanupWatchers` to an array and ensures
that all functions are retained.

In some local testing, I was able to trigger this, based on the
reproduction by @Gazler in
https://github.com/tailwindlabs/tailwindcss/pull/18559, to often call a
cleanup with more than one cleanup function in the array.

I'm going to paste the amazing reproduction from #18559 here as well:


# Requirements

We need a way to stress the CPU to slow down tailwind compilation, for
example stress-ng.

```
stress-ng --cpu 16 --timeout 10
```

It can be install with apt, homebrew or similar.

# Installation

There is a one-liner at the bottom to perform the required setup and run
the tailwindcli.

Create a new directory:

```shell
mkdir twtest && cd twtest
```

Create a package.json with the correct deps.

```shell
cat << 'EOF' > package.json
{
  "dependencies": {
    "@tailwindcss/cli": "^4.1.11",
    "daisyui": "^5.0.46",
    "tailwindcss": "^4.1.11"
  }
}
EOF
```

Create the input css:

```shell
mkdir src
cat << 'EOF' > src/.input.css
@import "tailwindcss" source(none);
@plugin "daisyui";
@source "../core_components.ex";
@source "../home.html.heex";
@source "./input.css";

EOF
```

Install tailwind, daisyui, and some HTML to make tailwind do some work:

```
npm install
wget https://raw.githubusercontent.com/phoenixframework/phoenix/refs/heads/main/installer/templates/phx_web/components/core_components.ex
wget https://github.com/phoenixframework/phoenix/blob/main/installer/templates/phx_web/controllers/page_html/home.html.heex
```

# Usage

This is easiest with 3 terminal windows:

Start a tailwindcli watcher in one terminal:

```shell
npx @tailwindcss/cli -i src/input.css -o src/output.css --watch
```

Start a stress test in another:

```shell
stress-ng --cpu 16 --timeout 30
```

Force repeated compilation in another:

```shell
for i in $(seq 1 50); do touch src/input.css; sleep 0.1; done
```

# Result

Once the stress test has completed, you can run:

```shell
touch src/input.css
```

You should see that there is repeated output, and the duration is in the
multiple seconds.

If this setup doesn't cause the issue, you can also add the `-p` flag
which causes the
CSS to be printed, slowing things down further:

```shell
npx @tailwindcss/cli -i src/input.css -p --watch
```

## One-liner

```shell
mkdir twtest && cd twtest
cat << 'EOF' > package.json
{
  "dependencies": {
    "@tailwindcss/cli": "^4.1.11",
    "daisyui": "^5.0.46",
    "tailwindcss": "^4.1.11"
  }
}
EOF

mkdir src
cat << 'EOF' > src/input.css
@import "tailwindcss" source(none);
@plugin "daisyui";
@source "../core_components.ex";
@source "../home.html.heex";
@source "./input.css";

EOF

npm install
wget https://raw.githubusercontent.com/phoenixframework/phoenix/refs/heads/main/installer/templates/phx_web/components/core_components.ex
wget https://github.com/phoenixframework/phoenix/blob/main/installer/templates/phx_web/controllers/page_html/home.html.heex
npx @tailwindcss/cli -i src/input.css -o src/output.css --watch
```

## Test plan

- Not able to reproduce this with a local build of the CLI after the
patch is applied but was able to reproduce it again once the patch was
reverted.

Co-authored-by: Gary Rennie <gazler@gmail.com>
2025-09-09 17:14:09 +02:00
Robin Malfait
b7c7e48c5d
Add @container-size utility (#18901)
This PR adds a new `@container-size` utility instead of
`@container-[size]`.

The main reason we didn't do this before is because we only have
container width related container queries, and not block based ones so
we never needed `size` and `inline-size` was enough.

However, `@container-size` is still useful if you are using container
query related units such as `cqb` which are using the block size of the
container not the inline size.

I also added a little helper such that `@container-size` is only
available in `insiders` and `4.2.0` (and later) so `4.1.x` releases
won't have this utility yet. This will require some CHANGELOG changes
such that we don't include this when releasing the next minor release.
2025-09-09 12:20:04 +00:00
Robin Malfait
2f1cbbfed2
Merge suggestions when using @utility (#18900)
This PR fixes a bug where custom `@utility` implementations with a name
that match an existing utility would override the existing suggestions
even though we generate both utilities.

With this, we want to make sure that both the custom and the built-in
utilities are suggested. We also want to make sure that we don't get
duplicate suggestions.

E.g.:

- `font-` would suggest:
  - 'font-black'
  - 'font-bold'
  - 'font-extrabold'
  - 'font-extralight'
  - 'font-light'
  - 'font-medium'
  - 'font-mono'
  - 'font-normal'
  - 'font-sans'
  - 'font-semibold'
  - 'font-serif'
  - 'font-thin'

But if you introduce this little custom utility:

```css
@theme {
  --custom-font-weights-foo: 123;
}

@utility font-* {
  --my-weight: --value(--custom-font-weights- *);
}
```

- `font-` would suggest:
  - 'font-foo'

With this fix, we would suggest:

- `font-` would suggest:
  - 'font-black'
  - 'font-bold'
  - 'font-extrabold'
  - 'font-extralight'
  - 'font-foo'          // This is now added
  - 'font-light'
  - 'font-medium'
  - 'font-mono'
  - 'font-normal'
  - 'font-sans'
  - 'font-semibold'
  - 'font-serif'
  - 'font-thin'

We also make sure that they are unique, so if you have a custom utility
that happens to match another existing utility (e.g. `font-bold`), you
won't see `font-bold` twice in the suggestions.

```css
@theme {
  --custom-font-weights-bold: bold;
  --custom-font-weights-normal: normal;
  --custom-font-weights-foo: 1234;
}

@utility font-* {
  --my-weight: --value(--custom-font-weights-*);
}
```

- `font-` would suggest:
  - 'font-black'
  - 'font-bold'          // Overlaps with existing utility
  - 'font-extrabold'
  - 'font-extralight'
  - 'font-foo'           // This is now added
  - 'font-light'
  - 'font-medium'
  - 'font-mono'
  - 'font-normal'        // Overlaps with existing utility
  - 'font-sans'
  - 'font-semibold'
  - 'font-serif'
  - 'font-thin'
2025-09-08 12:18:30 +02:00
Robin Malfait
77b3cb5318
Handle @variant inside @custom-variant (#18885)
This PR fixes an issue where you cannot use `@variant` inside a
`@custom-variant`. While you can use `@variant` in normal CSS, you
cannot inside of `@custom-variant`. Today this silently fails and emits
invalid CSS.
```css
@custom-variant dark {
  @variant data-dark {
    @slot;
  }
}
```
```html
<div class="dark:flex"></div>
```

Would result in:
```css
.dark\:flex {
  @variant data-dark {
    display: flex;
  }
}
```

To solve it we have 3 potential solutions:

1. Consider it user error — but since it generates CSS and you don't
really get an error you could be shipping broken CSS unknowingly.
1. We could try and detect this and not generate CSS for this and
potentially show a warning.
1. We could make it work as expected — which is what this PR does.

Some important notes:

1. The evaluation of the `@custom-variant` only happens when you
actually need it. That means that `@variant` inside `@custom-variant`
will always have the implementation of the last definition of that
variant.

In other words, if you use `@variant hover` inside a `@custom-variant`,
and later you override the `hover` variant, the `@custom-variant` will
use the new implementation.
1. If you happen to introduce a circular dependency, then an error will
be thrown during the build step.

You can consider it a bug fix or a new feature it's a bit of a gray
area. But
one thing that is cool about this is that you can ship a plugin that
looks like
this:
```css
@custom-variant hocus {
  @variant hover {
    @slot;
  }

  @variant focus {
    @slot;
  }
}
```

And it will use the implementation of `hover` and `focus` that the user
has defined. So if they have a custom `hover` or `focus` variant it will
just work.

By default `hocus:underline` would generate:
```css
@media (hover: hover) {
  .hocus\:underline:hover {
    text-decoration-line: underline;
  }
}

.hocus\:underline:focus {
  text-decoration-line: underline;
}
```

But if you have a custom `hover` variant like:
```css
@custom-variant hover (&:hover);
```

Then `hocus:underline` would generate:
```css
.hocus\:underline:hover, .hocus\:underline:focus {
  text-decoration-line: underline;
}
```

### Test plan

1. Existing tests pass
2. Added tests with this new functionality handled
3. Made sure to add a test for circular dependencies + error message
4. Made sure that if you "fix" the circular dependency (by overriding a
variant) that everything is generated as expected.

Fixes: https://github.com/tailwindlabs/tailwindcss/issues/18524
2025-09-05 12:24:11 +00:00
Robin Malfait
274be93fd8
Handle ' syntax in ClojureScript when extracting classes (#18888)
This PR fixes an issue where the `'` syntax in ClojureScript was not
handled properly, resulting in missing extracted classes.

This PR now supports the following ClojureScript syntaxes:

```cljs
; Keyword
(print 'text-red-500)

; List
(print '(flex flex-col underline))

; Vector
(print '[flex flex-col underline])
```

### Test plan

1. Added regression tests
2. Verified that we extract classes correctly now in various scenarios:

Top is before, bottom is with this PR:

<img width="1335" height="1862" alt="image"
src="https://github.com/user-attachments/assets/746aa073-25f8-41f8-b71c-ba83a33065aa"
/>


Fixes: #18882
2025-09-05 14:17:07 +02:00
Jordan Pittman
1334c99db8
Prepare v4.1.13 release (#18868) 2025-09-04 13:18:25 -04:00
Robin Malfait
65dc530f05
Do not allow variants to end with - or _ (#18872)
This PR is a followup of #18867, but this time we won't allow
`@custom-variant` to end with `-` or `_`.

The same reasoning applies here where Oxide doesn't pick this up but
Intellisense and Tailwind CSS' core does.

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-09-03 15:30:54 +00:00
Robin Malfait
54c3f308e9
Do not allow variants to start with - (#18867)
This PR fixes an issue where custom variants with just `-` in the name
were allowed but weren't actually picked up by Oxide so you couldn't use
them anyway.

The reason we allow `-` is for `kebab-style-variants`, which is very
common, but you shouldn't use `-`, `--` or more in a variant name.

It doesn't really solve the issue (#18863), but it fixes the
inconsistencies in that exist today.

Inconsistencies:
| &nbsp; | `-:flex` | `--:flex` |
| --: | :--: | :--: |
| Oxide | ❌ | ❌ |
| Tailwind Play | ✅ | ❌ |
| Intellisense | ✅ | ✅ |

- Oxide already had the correct rules setup, so this is expected
- Tailwind Play uses Tailwind's core compile step, but it considers
candidates that start with `--` as a CSS variable instead of a utility.
This means that the `--:flex` was considered a CSS variable and skipped
during compilation.
- Intellisense uses the same APIs than Tailwind's core, but it didn't
have the CSS variable check which resulted in the `--:flex` being
"correct".

With this PR, the matrix looks like this now:
| &nbsp; | `-:flex` | `--:flex` |
| --: | :--: | :--: |
| Oxide | ❌ | ❌ |
| Tailwind Play | ❌ | ❌ |
| Intellisense | ❌ | ❌ |


This should not be considered a breaking change because Oxide didn't
pick up candidates with variants that start with a `-`. CSS for these
candidates was never generated before.

Closes: #18863

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-09-03 14:32:18 +00:00
Robin Malfait
494051ca08
Consider variants starting with @- to be invalid (e.g. @-2xl:flex) (#18869)
This PR fixes a small parsing issue where variants such as `@-2xl:flex`
would parse, but were handled as-if they were `@2xl:flex` instead.

Noticed this while working on: #18867 

This is because when we parse normal variants like `data-foo` then we
want to have a `data` root and a `foo` value, not a `-foo` value.

If you are now using `@-2xl:flex`, then no CSS will be generated for
this anymore. If you were relying on this for some reason, you should
use `@2xl:flex` instead.

## Test plan

Before:

<img width="862" height="586" alt="image"
src="https://github.com/user-attachments/assets/b5993ca6-f907-49af-b5bd-b7206c8300e1"
/>

After:

<img width="862" height="586" alt="image"
src="https://github.com/user-attachments/assets/351f45e4-4cd3-451c-ae2a-c52c3e770629"
/>

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-09-03 14:07:32 +00:00
Robin Malfait
4637069087
update changelog 2025-09-02 11:26:17 +02:00
Jordan Pittman
5e2a160d8b
Drop exact duplicate declarations from output CSS within a style rule (#18809)
Fixes #18178

When someone writes a utility like `after:content-['foo']` it'll produce
duplicate `content: var(--tw-content)` declarations. I thought about
special casing these but we already have an optimization pass where we
perform a full walk of the AST, flattening some rules (with the `&`
selector), analyzing declarations, etc… We can utilize that existing
spot in core to analyze and remove duplicate declarations within rules
across the AST.

The implementation does this by keeping track of declarations within a
style rule and keeps the last one for any *exact duplicate* which is a
tuple of `(property, value, important)`. This does require some
additional loops but preseving the *last* declaration is important for
correctness with regards to CSS nesting.

For example take this nested CSS:
```css
.foo {
  color: red;
  & .bar {
    color: green;
  }
  color: red;
}
```

It expands to this:
```css
.foo {
  color: red;
}
.foo.bar {
  color: green;
}
.foo {
  color: red;
}
```

If you remove the *last* rule then a `<div class="foo bar">…</div>` will
have green text when its supposed to be red. Since that would affect
behavior we have to always preserve the last declaration for a given
property.

We could go further and eliminate multiple declarations for the same
property *but* this presents a problem: every property and value must be
understood and combined with browser targets to understand whether or
not that property may act as a "fallback" or whether definitely
overwrites its previous value in all cases. This is a much more
complicated task that is much more suited to something light Lighting
CSS.
2025-08-29 11:01:56 -04:00
Jordan Pittman
b1fb02a2d7
Hide internal fields from completions in matchUtilities (#18820)
The `__CSS_VALUES__` field is an internal field we use to transport data
about theme options from CSS throug hte JS plugin API. It wasn’t
supposed to show up in suggestions but we forgot to remove it from them.

Fixes #18812
2025-08-29 11:01:20 -04:00
Mateusz Bronis
7b03aca53f
Replace deprecated clip with clip-path in sr-only (#18769)
## Summary

This PR replaces the deprecated `clip` property used in the `sr-only`
utility with `clip-path`, and updates the corresponding reset in
`not-sr-only`.

- Closes
[tailwindlabs/tailwindcss#18768](https://github.com/tailwindlabs/tailwindcss/issues/18768)
- Replaces `clip: rect(0, 0, 0, 0);` with `clip-path: inset(50%);` in
`sr-only`
- Replaces `clip: auto;` with `clip-path: none;` in `not-sr-only`
- Updates unit test snapshots to reflect the new CSS output

Rationale:

- `clip` is deprecated and flagged by modern linters; `clip-path` is the
recommended modern alternative while preserving the intended
visually-hidden behavior.

Before:

```css
.sr-only {
  clip: rect(0, 0, 0, 0);
}

.not-sr-only {
  clip: auto;
}
```

After:

```css
.sr-only {
  clip-path: inset(50%);
}

.not-sr-only {
  clip-path: none;
}
```

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-08-28 11:05:38 -04:00
Robin Malfait
e578238da5
Migrate supports theme keys (#18817)
This PR is a follow up of #18815 and #18816, but this time let's migrate
the `supports` theme keys.

Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
  content: ['./src/**/*.html'],
  theme: {
    extend: {
      supports: {
        // Automatically handled by bare values (using CSS variable as the value)
        foo: 'foo: var(--foo)', // parentheses are optional
        bar: '(bar: var(--bar))',

        // Not automatically handled because names differ
        baz: 'qux: var(--foo)',
   //   ^^^   ^^^       ← different names

        // Custom
        grid: 'display: grid',
      },
    },
  },
}
```

Then we would generate the following Tailwind CSS v4 CSS:

```css
@custom-variant supports-baz {
  @supports (qux: var(--foo)) {
    @slot;
  }
}
@custom-variant supports-grid {
  @supports (display: grid) {
    @slot;
  }
}
```

Notice how we didn't generate a custom variant for `data-foo` or
`data-bar` because those are automatically handled by bare values.

I also went with the longer form of `@custom-variant`, we could use the
single selector approach, but that felt less clear to me.

```css
@custom-variant supports-baz (@supports (qux: var(--foo)));
@custom-variant supports-grid (@supports (display: grid));
```

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-08-28 14:50:48 +00:00
Robin Malfait
82034ec327
Migrate data theme keys (#18816)
This PR is similar to and a follow up of #18815, but this time to
migrate the `data` theme keys.

Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
  content: ['./src/**/*.html'],
  theme: {
    extend: {
      data: {
        // Automatically handled by bare values
        foo: 'foo',
    //  ^^^   ^^^       ← same names

        // Not automatically handled by bare values
        bar: 'baz',
    //  ^^^   ^^^       ← different names

        // Completely custom
        checked: 'ui~="checked"',
      },
    },
  },
}
```

Then we would generate the following Tailwind CSS v4 CSS:

```css
@custom-variant data-bar (&[data-baz]);
@custom-variant data-checked (&[data-ui~="checked"]);
```

Notice how we didn't generate a custom variant for `data-foo` because
those are automatically handled by bare values.
2025-08-28 14:45:18 +00:00
Robin Malfait
9e498a3e78
Migrate aria theme keys (#18815)
This PR migrates `aria` theme keys when migrating from Tailwind CSS v3
to v4.

While working on improving some of the error messages to get more
insights into why migrating the JS file changed
(https://github.com/tailwindlabs/tailwindcss/pull/18808), I ran into an
issue where I couldn't think of a good comment to why `aria` theme keys
were not being migrated. (Internally we have `aria` "blocked").

So instead of figuring out a good error message..., I just went ahead
and added the migration for `aria` theme keys.


Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
  content: ['./src/**/*.html'],
  theme: {
    extend: {
      aria: {
        // Built-in (not really, but visible because of intellisense)
        busy: 'busy="true"',

        // Automatically handled by bare values
        foo: 'foo="true"',
    //  ^^^   ^^^            ← same names

        // Not automatically handled by bare values because names differ
        bar: 'baz="true"',
    //  ^^^   ^^^            ← different names

        // Completely custom
        asc: 'sort="ascending"',
        desc: 'sort="descending"',
      },
    },
  },
}
```

Then we would generate the following Tailwind CSS v4 CSS:

```css
@custom-variant aria-bar (&[aria-baz="true"]);
@custom-variant aria-asc (&[aria-sort="ascending"]);
@custom-variant aria-desc (&[aria-sort="descending"]);
```

Notice how we didn't generate a custom variant for `aria-busy` or
`aria-foo` because those are automatically handled by bare values.

We could also emit a comment near the CSS to warn about the fact that
`@custom-variant` will always be sorted _after_ any other built-in
variants.

This could result in slightly different behavior, or different order of
classes when using `prettier-plugin-tailwindcss`.

I don't know how important this is, because before this PR we would just
use `@config './tailwind.config.js';`.
Edit: when using the `@config` we override `aria` and extend it, which
means that it would be in the expected order 🤔

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-08-28 16:40:38 +02:00
Jordan Pittman
8165e04564
Show suggestions for known matchVariant values (#18798)
Given this variant:
```js
matchVariant(
  "foo",
  (value) => `&:is([data-foo='${value}'])`,
  {
    values: {
      DEFAULT: "",
      bar: "bar",
      baz: "bar",
    },
  }
)
```

We weren't listing `foo-bar` and `foo-baz` in IntelliSense. This PR
fixes that.
2025-08-26 14:25:35 +00:00
Jordan Pittman
ee987e3f6a
Discard matchVariant matches with unknown named values (#18799)
This PR fixes two issues:
- When a variant is defined by `matchVariant` it could match unknown
values but not apply the variant (because it's unknown). This would
result in a utility being output that is the _same_ as a bare utility
without variants but a longer name. These were intended to be discarded
but weren't done so correctly.
- Similarly, when we encounter a known value but its not a string the
same thing would happen where we'd output a utility without applying the
variant. This was also intended to be discarded.

Basically given this code:
```js
matchVariant(
  "foo",
  (value) => `&:is([data-foo='${value}'])`,
  {
    values: {
      DEFAULT: "",
      bar: "bar",
      obj: { some: "object" },
    },
  }
)
```

And this HTML:
```html
<div class="foo-bar:bg-none foo-[baz]:bg-none foo-baz:bg-none foo-obj:bg-none"></div>
```

This CSS would be produced:
```css
@layer utilities {
  .foo-bar\:bg-none {
    &:is([data-foo='bar']) {
      background-image: none;
    }
  }
  /* this one shouldn't be here */
  .foo-baz\:bg-none {
    background-image: none;
  }
  /* this one shouldn't be here */
  .foo-obj\:bg-none {
    background-image: none;
  }
  .foo-\[baz\]\:bg-none {
    &:is([data-foo='baz']) {
      background-image: none;
    }
  }
}
```
2025-08-26 10:21:15 -04:00
Jordan Pittman
ce9b290b6b
Don't transition visibility when using transition (#18795)
We introduced an accidental breaking change a few months ago in 4.1.5
with #17812.

We added `visibility` to the property list in `transition` which
unfortunately only applies its change instantly when going from
invisible -> visible.

I've checked `display`, `content-visibility`, and `pointer-events` and
they apply their change instantly (as best I can tell) when
transitioning by default. And `overlay` only "applies" for discrete
transitions so it can stay as well.

The spec has this to say about [animating
`visibility`](https://www.w3.org/TR/web-animations-1/#animating-visibility):
> For the visibility property, visible is interpolated as a discrete
step where values of p between 0 and 1 map to visible and other values
of p map to the closer endpoint; if neither value is visible then
discrete animation is used.

This means that for visible (t=0) -> hidden (t=1) the timeline looks
like this:
- t=0.0: visible
- t=0.5: visible
- t=0.999…8: visible
- t=1.0: invisible

This means that for invisible (t=0) -> visible (t=1) the timeline looks
like this:
- t=0.0: invisible
- t=0.000…1: visible
- t=0.5: visible
- t=1.0: visible

So the value *is* instantly applied if the element is initially
invisible but when going the other direction this is not the case. This
happens whether or not the transition type is discrete.

While the spec calls out [`display` as working
similarly](https://drafts.csswg.org/css-display-4/#display-animation) in
practice this is only the case when `transition-behavior` is explicitly
set to `allow-discrete` otherwise the change is instant for both
directions.

Fixes #18793
2025-08-25 14:30:56 -04:00
zhe he
48f66dc835
Drop warning from browser build (#18732)
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-08-14 12:20:19 -04:00
Robin Malfait
6791e8133c
Prepare v4.1.12 release (#18728)
Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-08-14 14:35:49 +02:00
Robin Malfait
88a8234c8a
Mark .hdr and .exr as binary extensions (#18734)
This PR fixes an issue where `.hdr` files were scanned for candidates
even though it's a binary file.

As a workaround today, you could use:

```css
@source not "**/*.hdr";
```

To ignore `.hdr` files, but let's bake it in by default instead.

Fixes: #18733
2025-08-14 14:32:30 +02:00
Bill Criswell
1855d68cd7
Add --border-color to divide theme keys (#18704)
## Summary

In Tailwind 3 the border colors were able to be used with `divide`
utilities. I made it so that's true for Tailwind 4.

## Test plan

Just used `pnpm run tdd` and making it fails then making sure it passes.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-08-13 16:44:44 +02:00
Jordan Pittman
6bfdb7c60e
Bump Bun (#18723)
Fixes #18695

Was waiting for 1.2.20 b/c of some build failures. Hopefully fixed now.
Also appears to fix the above linked bug about Windows symlinks.

[ci-all]
2025-08-13 16:29:52 +02:00
Robin Malfait
30be24b29f
Fix false-positive migrations in addEventListener and JavaScript variable names (#18718)
This PR fixes 2 false-positives when running the upgrade tool on a
Tailwind CSS v3 project converting it to a Tailwind CSS v4 project.

The issue occurs around migrations with short simple names that have a
meaning outside if Tailwind CSS, e.g. `blur` and `outline`.

This PR fixes 2 such cases:


1. The `addEventListener` case:

   ```js
   document.addEventListener('blur', handleBlur)
   ```

We do this by special casing the `addEventListener(` case and making
sure the first argument to `addEventListener` is never migrated.

2. A JavaScript variable with default value:

   ```js
   function foo({ foo = "bar", outline = true, baz = "qux" }) {
     // ...
   }
   ```

The bug is relatively subtle here, but it has actually nothing to do
with `outline` itself, but rather the fact that some quote character
came before and after it on the same line...

One of our heuristics for determining if a migration on these small
words is safe, is to ensure that the candidate is inside of a string.
Since we didn't do any kind of quote balancing, we would consider the
`outline` to be inside of a string, even though it is not.

So to actually solve this, we do some form of quote balancing to ensure
that it's _not_ inside of a string in this case.

Additionally, this PR also introduces a small refactor to the
`is-safe-migration.test.ts` file where we now use a `test.each` to
ensure that failing tests in the middle don't prevent the rest of the
tests from running.

### Test plan

1. Added dedicated tests for the cases mentioned in the issue (#18675).
2. Added a few more tests with various forms of whitespace.

Fixes: #18675
2025-08-12 09:48:35 -04:00
Jordan Pittman
68a79b159c
Suggest bare values for flex-* utilities (#18642)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1426

We weren't suggesting things like `flex-1`. This adds that. I'm not sure
if we want to suggest more `flex-<number>` values though. I've added
1–12 but am open to changing this.
2025-07-30 10:48:26 -04:00
Jordan Pittman
492304212f
Allow users to disable url rewriting in the PostCSS plugin (#18321)
Since we (optionally) support source maps now it's possible that a later
PostCSS plugin that *still* does url rewriting might fail to do so
correctly because nodes will have preserved source locations in dev
builds where before we "pretended" that everything came from the
original file.

But, because we can't know if such a plugin is present, disabling this
behavior when source maps are enabled could cause issues *and* would be
a breaking change.

I wish everything *could just work* here but realistically we can't know
what plugins have run before our PostCSS plugin or what plugins will run
after so the best option (I think) we can offer here is to allow users
to disable url rewriting at the plugin level.

Fixes #16700
2025-07-30 10:35:10 -04:00
Jordan Pittman
fa3f45f02c
Don’t output CSS objects with false or undefined in the AST (#18571)
Fixes https://github.com/tailwindlabs/tailwindcss-typography/issues/384

Basically when addUtilities/addComponents/matchUtilities/matchComponents
saw a value of `false` it was being output instead of being discarded
like it was in v3.

The types really require these to be strings but for things like the
typography plugin this isn't really carried through from its theme
config so it was easy to put anything in there and not realize it
doesn't match the expected types.

Basically this:
```js
addUtilities({
  '.foo': {
    a: 'red',
    'z-index': 0,
    '.bar': false,
    '.baz': null, // this one already worked
    '.qux': undefined,
  },
})
```

Now works like it did in v3 and omits `.bar`, `.baz`, and `.qux`
2025-07-21 15:39:34 -04:00
Jordan Pittman
939fda6f4e
Show more specific error for functional-like but invalid utility names (#18568)
Fixes #18566

The behavior is by design but the error message could definitely be
improved.
2025-07-18 12:31:19 -04:00
Hugo Parente Lima
298ad08653
Add support for Slang template (#18565)
## Summary

Slang is basically a Slim template language for Crystal language, so the
very same Slim parser works fine.

Slim template: https://github.com/slim-template/slim
Slang template: https://github.com/jeromegn/slang

## Test plan

Create a simple slang file with some tailwind-css and check if the CSS
is being extracted:
```slim
doctype html
html
  head
    title This is a title
  body.min-h-screen
    header.stick.top-0.z-10
      section.max-w-4xl.mx-auto.p-4.flex.items-center.justify-between
        h1.text-3xl.font-medium This is a slang file
``` 
To test it, get any slim template, rename the extension to .slang 

Fixes #17851

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-07-17 14:10:19 -04:00
Jordan Pittman
88b9f15b65
Center the dropdown icon added to an input with a paired datalist in Chrome (#18511)
This PR tweaks the dropdown arrow added to an input by Chrome when it
has a `list` attribute pointing to a `<datalist>`.

Right now the arrow isn't centered vertically:

<img width="227" height="58" alt="Screenshot 2025-07-14 at 15 41 50"
src="https://github.com/user-attachments/assets/b354a5e8-432d-432d-bfe4-f7b6f6683548"
/>

The cause of this is the line height being inherited into the pseudo
element which controls how the marker is positioned. I *think* this is
because it's being drawn with unicode symbols but I'm not sure. It could
just be from the `list-item` display.

After this PR changes the line height its centered again:

<img width="227" height="58" alt="Screenshot 2025-07-14 at 15 42 05"
src="https://github.com/user-attachments/assets/1afa1f33-cc28-4b1f-9e04-e546f6848f57"
/>

Some notes:

This only affects Chrome and also does not appear to cause issues for
date/time inputs. While weird that this pseudo is the one used for a
`<datalist>` marker it is indeed correct.

Fixes #18499

Can use this Play to test the change:
https://play.tailwindcss.com/jzT35CRpr0

---------

Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
2025-07-15 14:41:04 -04:00
Jordan Pittman
798a7bf905
Ignore consecutive semicolons in the CSS parser (#18532)
Fixes #18523

I swear I'd already landed this but apparently not.
2025-07-14 11:44:31 -04:00
Jelf
fac8cd9def
fix: allow process.env.DEBUG to be a boolean in @tailwindcss/node (#18485)
TanStack Start build to `cloudflare-module`, `debug` value type is
boolean.
reference:
https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack/

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-07-11 11:51:50 -04:00
Jordan Pittman
2941a7b5c2
Track source locations through @plugin and @config (#18329)
Fixes https://github.com/tailwindlabs/tailwindcss-forms/issues/182

Inside JS plugins and configs we weren't tracking source location info
so using things like `addBase(…)` could result in warnings inside Vite's
url rewriting PostCSS plugin because not all declarations had a source
location.

The goal here is for calls to `addBase` to generate CSS that points back
to the `@plugin` or `@config` that resulted in it being called.
2025-07-03 11:20:54 -04:00
Jordan Pittman
8c8033017d
Detect classes inside Elixir charlist, word list, and string sigils (#18432)
Fixes #18431

The `~W(…)` syntax in Elixir is a _sigil_. It's basically another way to
write strings, arrays of strings, etc… The syntax lets you use a handful
of surrounding brackets like `~W(…)`, `~W[…]`, `~W{…}`, `~W"…"`, etc… to
let you write lists without necessarily having to escape characters.

In v3 our extractor was able to pick these up but in v4 Oxide does not.
I've added a preprocessor for Elixir files so we can modify the code
before our main extractor sees it.

Now things like this: `~W(text-white bg-gray-600)` will get turned into
` ~W text-white bg-gray-600 ` which can easily be processed by our
extractor.

The sigils we support are:
- `~s` / `~S` (strings)
- `~w` / `~W` (word lists)
- `~c` / `~C` (charlists)

We're specifically detecting the use of `(…)`, `[…]`, and `{…}` as using
quotes already works today.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-07-02 16:01:13 -04:00
Henrik Eneroth
05b65d59b5
Fix trailing ) from interfering with extraction in Clojure keywords (#18345)
## Summary

In a form like, 

```clojure 
(if condition :bg-white :bg-black)
```

`:bg-black` will fail to extract, while `:bg-white` is extracted as
expected. This PR fixes this case, implements more comprehensive
candidate filtering, and supersedes a previous PR.

Having recently submitted a PR for handling another special case with
Clojure keywords (the presence of `:` inside of keywords), I thought it
best to invert the previous strategy: Instead of handling special cases
one by one, consume keywords according to the Clojure reader spec.
Consume nothing else, other than strings.

Because of this, this PR is a tad more invasive rather than additive,
for which I apologize. The strategy is this:
- Strings begin with a `"` and ends with an unescaped `"`. Consume
everything between these delimiters (existing case).
- Keywords begin with `:`, and end with whitespace, or one out of a
small set of specific reserved characters. Everything else is a valid
character in a keyword. Consume everything between these delimiters, and
apply the class splitting previously contained in the outer loop. My
previous special case handling of `:` inside of keywords in #18338 is
now redundant (and is removed), as this is a more general solution.
- Discard _everything else_. 

I'm hoping that a strategy that is based on Clojure's definition of
strings and keywords will pre-empt any further issues with edge cases.

Closes #18344.

## Test plan
- Added failing tests.
- `cargo test` -> failure
- Added fix
- `cargo test` -> success

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-30 12:23:38 -04:00
Jordan Pittman
2030e942bc
Don't consider the global important state in @apply (#18404)
Fixes #18400

In v3 when you used `important: true` it did not affect `@apply`.
However, in v4 it does and there's no way to make it *not*. This is
definitely a bug and would be unexpected for users coming from v3 who
use `@apply` and `important` together.

Basically, the following code, along with the detected utility `flex` in
source files…

```css
@import 'tailwindcss/utilities' important;
.flex-explicitly-important {
  @apply flex!;
}
.flex-not-important {
  @apply flex;
}
```

… would output this:
```css
.flex {
  display: flex !important;
}
.flex-explicitly-important  {
  display: flex !important;
}
.flex-not-important {
  display: flex !important;
}
```

But it's expected that `@apply` doesn't consider the "global" important
state. This PR addresss this problem and now the output is this:

```css
.flex {
  display: flex !important;
}
.flex-explicitly-important  {
  display: flex !important;
}
.flex-not-important {
  display: flex; /* this line changed */
}
```

If you want to mark a utility as important in `@apply` you can still use
`!` after the utility to do so as shown above.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-06-27 11:55:15 -04:00
Robin Malfait
b24457a9f4
Prepare 4.1.11 (#18397) 2025-06-26 12:43:17 +02:00
Rózsa Zoltán
aa859314d9
feat: add Vite 7 support to the @tailwindcss/vite plugin (#18384)
Closes #18381 

* [Changelog for Vite 7.0.0
(2025-06-24)](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md#700-2025-06-24)

Starting from Vite 7, Node 18 support will be dropped, which doesn't
really affect Tailwind. It might be worth mentioning in the
documentation that the recommended minimum Node versions are 20.19 and
22.12.

Vite 7 is only available in ESM format, which is also not an issue.

Vite's browser support aligns with the v4 guidelines:
```
Chrome 87 → 107       (tw: 111)
Edge 88 → 107         (tw: 111)
Firefox 78 → 104      (tw: 128)
Safari 14.0 → 16.0    (tw: 16.4)
```
* [Vite 7 - Browser
Support](https://vite.dev/guide/migration.html#default-browser-target-change)
* [Tailwind CSS v4 - Browser
Support](https://tailwindcss.com/docs/compatibility#browser-support)

So, at first glance, there's nothing more to do except enabling support
for these versions.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-24 12:31:17 -04:00
Jordan Pittman
c5a997cbdb
Document --watch=always in the CLI (#18337)
Closes https://github.com/tailwindlabs/tailwindcss.com/issues/1835

We had this in v3 as an undocumented option. We still have it in v4 but
it's not documented under the CLI usage but should be. This PR adds
this.

**before**
<img width="803" alt="Screenshot 2025-06-18 at 09 41 40"
src="https://github.com/user-attachments/assets/c3becf11-e31d-4355-9d23-bddd0b2fc4a6"
/>

**after**
<img width="1152" alt="Screenshot 2025-06-18 at 09 41 20"
src="https://github.com/user-attachments/assets/4f61a156-680d-4f39-b92d-7f0f63270689"
/>
2025-06-19 14:52:47 -04:00
Jordan Pittman
75cbfc251d Update changelog 2025-06-18 12:38:54 -04:00
Jordan Pittman
5fc6698b7b
Add heuristic to skip candidate migrations inside emit(…) (#18330)
Fixes #18318
2025-06-17 12:49:33 -04:00
Robin Malfait
7f97179b66
update changelog
drop the `:`
2025-06-11 23:21:57 +02:00
Robin Malfait
2ebaff214a
make changelog entry consistent 2025-06-11 23:07:12 +02:00
Robin Malfait
d06bbb80da
Prepare v4.1.10 (#18290) 2025-06-11 22:26:51 +02:00
Robin Malfait
ddb0beff66
Fix missing space around - when using % regression (#18289)
This PR fixes a regression we shipped in v4.1.9, when using arbitrary
values and injecting spaces around operator.

When you use `w-[calc(100%-var(--foo))]`, you expect that this generates
valid CSS:

```css
width: calc(100% - var(--foo));
```

But due to a regression, we generated:

```css
width: calc(100%-var(--foo));
```

Which is invalid CSS.

This is because the algorithm we used to know when we had to inject a
space around the `-` didn't take the `%` sign into account.

We also didn't handle uppercase units like `123PX` properly. This PR
fixes both issues.

## Test plan

1. Added a regression test for the `%`
2. Added a regression test for uppercase units like `123PX`

Fixes: #18288
2025-06-11 20:18:14 +00:00