Commit graph

608 commits

Author SHA1 Message Date
Robin Malfait
cbbbe84475
Release 4.1.16 (#19185) 2025-10-23 12:32:27 +02:00
Robin Malfait
601d6719f8
Fix incorrect colors used in pseudo-element (#19184)
This PR essentially reverts
https://github.com/tailwindlabs/tailwindcss/pull/19069

We added the nested `&` inside the `@supports` query when we create
fallbacks for color-mix so that devtools (Safari) doesn't freak out.
This works in most cases, however, if you have a parent pseudo element
like `::before`, then the browser will not allow the nested `&`
resulting in invalid CSS.

This PR means that we go back to the broken devtools experience in
Safari, but at least the CSS is valid and works as expected.

Fixes: #19183
2025-10-23 10:07:01 +00:00
Jordan Pittman
a41add9fab
Improve canonicalization for & > :pseudo and & :pseudo arbitrary variants (#19178)
This improves canonicalization of arbitrary variants that use pseudo
classes a bit.

Before this we would see `[&_:first-child]:flex` and leave it be when it
can instead be written as `**:first:flex`. Likewise, for pseudo classes
that don't have a variant, we can still simplify things a bit as
`[&_:--custom]` can be written `**:[:--custom]`.
2025-10-22 08:29:08 -04:00
Jordan Pittman
0113b88fbd
Fix canonicalization of arbitrary variants with attribute selectors (#19176)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1481

We were detecting when we needed to apply the `*` and `**` variants and
even detecting attribute selectors. However we only cleaned up the
attribute selectors when they were `data-*` or `aria-*` attributes. This
resulted in a "loop" of sorts because we'd:
- See something like `[&_>_[foo]]:flex`
- Notice that it needs a `*` variant
- Add the `*` variant, giving `*:[&_>[foo]]:flex`
- But fail to cleanup the remainder of the variant

This then meant that we'd see the `*:[&_>[foo]]:flex` the next time we
checked, notice that it still needed a `*` variant, and repeat…

This PR fixes this case to clean up the selector.
2025-10-22 08:19:54 -04:00
Jordan Pittman
29687e0183
Discard candidates with an empty data type (#19172)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1479

Maybe should close
https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1480 —
perhaps we can find a workaround there for older versions?

We're building up a class name in code to validate if something is a
valid variant: `{variant}:[color:red]`

if `{variant}` got replaced with `bg-[` then we'd produce
`bg-[:[color:red]` and this parsed as a valid candidate:
```
bg-[:[color:red]
^^                root: `bg`
   ^              data type: `` (empty string) — this should be invalid
     ^^^^^^^^^^   value: `[color:red`
```

The value isn't valid _but_ the syntax for arbitrary values is pretty
lax in core. Oxide already won't pick something like this up though so
no problem there. Only a problem for something like IntelliSense or
clients using the compile() API directly.
2025-10-22 06:14:43 -04:00
Robin Malfait
56e7f3b2c2
Improve memory usage during canonicalization (#19171)
This PR drastically improves the memory usage when performing
canonicalization if you swap out the underlying DesignSystem often. This
will be most noticeable in Intellisense.

The big issue we had is that we used module scoped Map objects where we
cache data based on the DesignSystem. If you then create new design
systems (often), then the cache would just keep growing and growing.

This PR solves that by essentially storing all the caches on the Design
System itself. This way, when you throw away a Design System, all the
caches go with it.

Another approach would've been to use a WeakMap, but then we would have
to make sure that no strong references to the DesignSystem exist
anywhere else, otherwise we would still have the same memory issues.

Note: make sure to go commit by commit and use `?w=1` to ignore
whitespace changes.

## Test plan

1. All existing tests pass

Not super sure how to test this as part of the test suite without making
it slow But I also don't think that's super necessary either. Here is an
experiment I did where I introduce 5 design systems:
<img width="1326" height="274" alt="image"
src="https://github.com/user-attachments/assets/817025e3-0f5b-44be-949b-54ed08f5b3fb"
/>

On the current `main` branch, this looks like:
<img width="619" height="69" alt="image"
src="https://github.com/user-attachments/assets/588ae99b-c978-4c01-bfd1-5cc0725723a8"
/>


In this PR, the memory usage looks like:
<img width="512" height="56" alt="image"
src="https://github.com/user-attachments/assets/0052ad21-7b99-4edf-8a14-8ccef52362db"
/>

The memory usage is stable, but to actually prove that we can still
track multiple design systems, let's track them all in a `Set` so
garbage collection cannot get rid of the unused design system objects:
<img width="847" height="230" alt="image"
src="https://github.com/user-attachments/assets/5f044927-3d53-4c15-8145-78eb2b4d6d54"
/>

Now we're sort of back to the current situation on `main`:
<img width="507" height="53" alt="image"
src="https://github.com/user-attachments/assets/868c0238-8646-41ce-8151-e0ef6dd17d64"
/>
2025-10-21 16:55:30 +02:00
Jordan Pittman
3a4ab8201b
Stop suggesting legacy utilities (#19169)
It was already supposed to work this way — and it appeared via the tests
that it did — but the tests weren't loading the design system normally
so when we split the legacy utilities into a separate file it stopped
testing this properly.

The setup here is a bit iffy but the gist is:
- We allow static utilities to be suggested by default
- A static utility can *opt out* of suggestions by explicitly
registering themselves to return none.
2025-10-21 06:54:10 -04:00
Robin Malfait
7537e34fd1
Ignore --tw- variables during internal signature computation (#19156)
This PR improves some of the signature computation logic. Right now,
when you want to convert `[font-weight:400]` to `font-normal` it's not
going to work because the signatures don't like up:

```css
/* [font-weight:400] */
.x {
  font-weight: 400;
}

/* font-normal */
.x {
  --tw-font-weight: 400;
  font-weight: 400;
}
```

So this PR essentially ignores `--tw-{property}` _if_ the `{property}`
exists with the exact same value.

The main reason we have this, is to make composition of utilities
easier.

As with any of these upgrades, they are the expected behavior in most
cases, but there could always be a case where you don't want this, but
that's why the upgrade tool requires you to review each change before
applying it. Intellisense will recommend the simplified class, but it's
up to you to decide if you want to apply it or not.

There is a known edge case for `leading-{num}`, because the property is
`line-height` and the CSS variable is `--tw-leading`. Right now we map
`--tw-leading` to `line-height` but we can also update the leading
classes to use `--tw-line-height` instead but that feels like a bigger
breaking change _if_ people rely on these internal CSS variables...
2025-10-20 15:21:07 +00:00
Robin Malfait
66c18ca8a4
Collapse multiple utilities (#19147)
Some commits look like a lot of changes, but they just move things
around, so best to use `?w=1` when viewing commit by commit.

This PR adds a new feature to the `designSystem.canonicalizeCandidates`
to collapse multiple utilities to fewer utilities. To make this
possible, we also have to convert some logical properties to physical
properties (controllable via an option).

```ts
ds.canonicalizeCandidates(['w-4', 'h-4'], {
  collapse: true // Default `true`
  logicalToPhysical: true // Default `true`
}) // → ['size-4']
```

We can already compute the signature of each utility, where if two
utilities have the same signature they are considered the same.

However it's kind of impossible to generate all combinations of all
utilities ever to figure out if there is a potential collapse possible.
Even if we just focus on the incoming list of candidates, there could
still be a lot of classes. So instead we have to be a bit more clever.

First, we group candidates together by the used variants and if the
important `!` flag was used. We can improve this in the future, but for
now we won't even try to combine `hover:w-4 h-4`.

Next, for each candidate, we figure out which property and value it
uses. We can build up a lookup table for this. We already did this
process for all utilities in the system as well.

The lookup table for `w-4 h-4 p-4` might look something like this.

```json
{
  "width": {
    "16px": ["w-4", "size-4"],
  },
  "height": {
    "16px": ["h-4", "size-4"],
  },
  "padding": {
    "16px": ["p-4"],
  }
}
```

Next, we can build groups of candidates where an intersection exists in
the lookup table. In the example above, we can see that `w-4` and `h-4`
both map to `size-4` for the value `16px`. So we can group these two
candidates. The `p-4`d doesn't intersect with anything else, so it
remains alone. This also means that we only have to generate
combinations for two candidates (2^2 = 4) instead of three (2^3 = 8). In
practice, your class list might have many classes, so keeping this
number low is important.

When we generate combinations, we will generate the most amount of
candidates first so we have the largest collapse possible. We also stop
when we reach <= 1 combinations because we need at least two candidates
to collapse.

Since this uses the internal design system, if you have custom
`@utility`s, this will work as expected.

```css
@utility example {
  @apply border rounded p-4;
}
```

If you then use:
```html
<div class="border m-0 rounded p-4"></div>
```

Then we can collapse this to:
```html
<div class="example m-0"></div>
```

But even if we used:
```html
<div class="border m-0 rounded pt-4 pb-4 px-4"></div>
```

It would still collapse to:
```html
<div class="example m-0"></div>
```

...because the `pt-4` and `pb-4` can collapse to `py-4`, and `py-4` and
`px-4` can collapse to `p-4`.

This is also where that logical to physical conversion comes into play,
because while `pt-4` and `pb-4` set the physical `padding-top` and
`padding-bottom` properties. The `py-4` utility sets the logical
`padding-block` property. So we internally transform `padding-block` to
the `padding-top` and `padding-bottom` physical properties. The funny
thing is that this logical to physical conversion actually means that we
will convert `pt-4` and `pb-4` from _physical_ to _logical_ properties,
because we converted to `py-4`...

In _most_ cases it's fine, and even preferred to use `py-1` over `pt-1
pb-1`, but in case it's not, then you can disable the
`logicalToPhysical` option.

## Test plan

Added some dedicated tests for this new functionality.
2025-10-20 15:15:31 +00:00
Robin Malfait
b2e2435ccb
Release 4.1.15 (#19159) 2025-10-20 14:48:42 +02:00
Robin Malfait
3b636b74d7
Mark break-words as deprecated, and upgrade to wrap-break-word (#19157)
This PR marks `break-words` as deprecated (such that intellisense
doesn't suggest it anymore). Updates the upgrade tooling to prefer
`wrap-break-word` instead.

Note: `break-words` will still work as expected.

## Test plan

1. `break-words` still generates the correct CSS.
2. Intellisense doesn't suggest `break-words` anymore.
3. Upgrade tooling suggests `wrap-break-word` instead of `break-words`.
2025-10-20 14:14:25 +02:00
Robin Malfait
acb27ef9e9
Generalize the walk implementation (#19126)
This PR generalizes the `walk` implementations we have. What's important
here is that we currently have multiple `walk` implementations, one for
the AST, one for the `SelectorParser`, one for the `ValueParser`.

Sometimes, we also need to go up the tree in a depth-first manner. For
that, we have `walkDepth` implementations.

The funny thing is, all these implementations are very very similar,
even the kinds of trees are very similar. They are just objects with
`nodes: []` as children.

So this PR introduces a generic `walk` function that can work on all of
these trees.

There are also some situations where you need to go down and back up the
tree. For this reason, we added an `enter` and `exit` phase:

```ts
walk(ast, {
  enter(node, ctx) {},
  exit(node, ctx) {},
})
```

This means that you don't need to `walk(ast)` and later `walkDepth(ast)`
in case you wanted to do something _after_ visiting all nodes.

The API of these walk functions also slightly changed to fix some
problems we've had before. One is the `replaceWith` function. You could
technically call it multiple times, but that doesn't make sense so
instead you always have to return an explicit `WalkAction`. The
possibilities are:

```ts
// The ones we already had
WalkAction.Continue // Continue walking as normal, the default behavior
WalkAction.Skip // Skip walking the `nodes` of the current node
WalkAction.Stop // Stop the entire walk

// The new ones
WalkAction.Replace(newNode) // Replace the current node, and continue walking the new node(s)
WalkAction.ReplaceSkip(newNode) // Replace the current node, but don't walk the new node(s)
WalkAction.ReplaceStop(newNode) // Replace the current node, but stop the entire walk
```

To make sure that we can walk in both directions, and to make sure we
have proper control over when to walk which nodes, the `walk` function
is implemented in an iterative manner using a stack instead of
recursion.

This also means that a `WalkAction.Stop` or `WalkAction.ReplaceStop`
will immediately stop the walk, without unwinding the entire call stack.

Some notes: 
- The CSS AST does have `context` nodes, for this we can build up the
context lazily when we need it. I added a `cssContext(ctx)` that gives
you an enhanced context including the `context` object that you can read
information from.
- The second argument of the `walk` function can still be a normal
function, which is equivalent to `{ enter: fn }`.

Let's also take a look at some numbers. With this new implementation,
each `walk` is roughly ~1.3-1.5x faster than before. If you look at the
memory usage (especially in Bun) we go from `~2.2GB` peak memory usage,
to `~300mb` peak memory usage.

Some benchmarks on small and big trees (M1 Max):

<img width="2062" height="1438" alt="image"
src="https://github.com/user-attachments/assets/5ec8c22a-9de8-4e08-869a-18c0d30eb7e8"
/>

<img width="2062" height="1246" alt="image"
src="https://github.com/user-attachments/assets/e89d4b8e-29ca-4aee-8fd2-b7c043d3bbf4"
/>

We also ran some benchmarks on @thecrypticace's M3 Max:

<img width="1598" height="1452" alt="image"
src="https://github.com/user-attachments/assets/3b06b6fe-2497-4f24-a428-1a0e2af3896a"
/>

In node the memory difference isn't that big, but the performance itself
is still better:

<img width="2034" height="1586" alt="image"
src="https://github.com/user-attachments/assets/ef28ae14-b53e-4912-9621-531f3b02898f"
/>


In summary:

1. Single `walk` implementation for multiple use cases
2. Support for `enter` and `exit` phases
3. New `WalkAction` possibilities for better control
4. Overall better performance
5. ... and lower memory usage

## Test plan

1. All tests still pass (but had to adjust some of the APIs if `walk`
was used inside tests).
2. Added new tests for the `walk` implementation
3. Ran local benchmarks to verify the performance improvements
2025-10-15 21:28:12 +02:00
Robin Malfait
de6b54c307
Fix parsing issue when \t is used in at-rules (#19130)
This PR fixes an issue where an at-rule that used `@name\tparams` didn't
properly parse because we didn't properly handle `\t`.

## Test plan

1. Added failing tests
2. Made them pass

Fixes: #19127
2025-10-15 11:50:02 +02:00
Robin Malfait
22858ac4ae
Make TypeScript a bit more happy (#19124)
While working on another PR, I noticed that some files had missing
properties and made TypeScript unhappy. Let's make TypeScript happy
again...
2025-10-14 19:52:46 +00:00
Jordan Pittman
f945c3d29f
Don’t index into strings with the theme(…) function (#19111)
Indexing into a string is only ever going to produce a single character
and is almost guaranteed to be a mistake. The legacy keypath notation is
meant to traverse objects and arrays — not strings.

Fixes #19104
2025-10-13 11:44:33 -04:00
Jordan Pittman
2cba8319ce
Clone AST nodes used in staticValues (#19110)
These were getting mutated but they were shared instead of being
re-created for new candidates. Cloning the nodes fixes this so mutation
of the AST nodes doesn’t stick around.

Fixes #19108
2025-10-13 10:23:09 -04:00
Robin Malfait
c67c0c54cf
Canonicalize dimensions (#19101) 2025-10-10 17:44:53 +02:00
Robin Malfait
01d1e98259
Canonicalization constant folding and handling zeros (#19095)
The main goal of this PR was to support canonicalization of zero like
values. We essentially want to canonicalize `-mt-0` as `mt-0`, but also
`mt-[0px]`, `mt-[0rem]`, and other length-like units to just `mt-0`.

To do this, we had to handle 2 things:

1. We introduced some more constant folding, including making `0px` and
`0rem` fold to `0`. We only do this for length units. We also normalize
`-0`, `+0`, `-0.0` and so on to `0`.
2. While pre-computing utilities in our lookup table, we make sure that
we prefer `mt-0` over `-mt-0` if both result in the same signature.

Moved some of the constant folding logic into its own function and added
a bunch of separate tests for it.

## Test plan

Added more unit tests where we normalize different zero-like values to
`0`.

Running the canonicalization logic:
```js
designSystem.canonicalizeCandidates([
  '-m-0',
  '-m-[-0px]',
  '-m-[-0rem]',
  '-m-[0px]',
  '-m-[0rem]',
  'm-0',
  'm-[-0px]',
  'm-[-0rem]',
  'm-[0px]',
  'm-[0rem]',
  'm-[calc(var(--spacing)*0)]',
  'm-[--spacing(0)]',
  'm-[--spacing(0.0)]',
  'm-[+0]',
  'm-[-0]',
  '-m-[-0]',
  '-m-[+0]',
]) // → ['m-0']
```

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-10-09 18:09:29 +00:00
Robin Malfait
3aadba7cc1
Move modifier of not-*, has-*, and in-* variant to sub variant (#19100)
This PR fixes an issue where a compound variant with a modifier such as
`not-group-hover/name:flex` would not generate anything because the
`/name` modifier belongs to the `not` variant, and not the compounded
`group-hover` variant.

This PR is a **workaround** (and definitely not perfect) by special
casing the `not`, `has`, and `in` variants such that their modifiers are
moved internally to the sub variant as-if the `/name` existed on
`group-hover`.

We don't do it for other compound variants such as `group` and `peer`
because then `group-peer-focus/name:underline` would result in a
breaking change:

```diff
- .group-peer-focus\\/name\\:flex:is(:where(.group\\/name):is(:where(.peer):focus ~ *) *)
+ .group-peer-focus\/name\:flex:is(:where(.group):is(:where(.peer\/name):focus ~ *) *) 
```

In case the diff is not clear, the name has moved:
<img width="1219" height="78" alt="image"
src="https://github.com/user-attachments/assets/dce7bc95-9d93-452d-a275-b3891a05a1a4"
/>


This is also a limited workaround, because if you need multiple
modifiers it won't work. I would've loved to special case this _inside_
the `not`, `has`, and `in` code that handles these variants, but we
handle the variants in a depth-first way, so by the time you are
handling the `not` variant, the sub variant was already handled...

In a perfect world, you can use something like `not-group/name-hover`
but then it becomes unambiguous because is `name` the name, is
`name-hover`?

## Test plan

Added a new test that wouldn't generate anything before this fix.

Fixes: #15772
2025-10-09 19:30:01 +02:00
Robin Malfait
0c14df1a15
Fix resolving colors via theme(…) in compat mode with nested objects (#19097)
This PR fixes an issue when loading (nested) colors from a config file
and later referencing it via the `theme(…)` function in CSS.

Given a config like this:

```js
module.exports = {
  theme: {
    colors: {
      foo: 'var(--foo-foo)',
      'foo-bar': 'var(--foo-foo-bar)',
    },
  },
}
```

We internally map this into the design system. The issue here is that
the `foo` and `foo-bar` are overlapping and it behaves more like this:

```js
{
  foo: {
    DEFAULT: 'var(--foo-foo)',
    bar: 'var(--foo-foo-bar)'
  },
}
```

So while we can easily resolve `colors.foo-bar`, the `colors.foo` would
result in the object with a `DEFAULT` key. This PR solves that by using
the `DEFAULT` key if we end up with an object that has it.

If you end up resolving an object (`theme(colors)`) then the behavior is
unchanged.


## Test plan

1. Added a test based on the config in the issue (which failed before
this fix).
2. Also simplified the test case after identifying the problem (with the
`DEFAULT` key).

Fixes: #19091
2025-10-09 17:10:32 +00:00
Robin Malfait
0c8d881f0e
Improve percentage canonicalization (#19072)
This PR improves the canonicalization of percentage values such that
`[.1]`, `[.10]`, `[10%]` and `[10.0%]` are all treated as the same
value.

Right now we're only focusing on percentages. We can likely do this for
all numbers, but I'm a little afraid of places where you can have
multiple numbers separated by multiple dots (think SVGs).

## Test plan

1. Added more tests to cover the new cases.
2. Tested it in a local test project, where you can see the
normalization in action.

<img width="1383" height="117" alt="image"
src="https://github.com/user-attachments/assets/03d99e3a-4404-437b-b458-58f7e8ce60da"
/>
2025-10-07 09:47:22 -04:00
Robin Malfait
efe084b7e7
Improve performance of cloning AST nodes (#19067)
This PR improves the performance of when we need to clone some AST
nodes. We have a few places where we clone `Candidate`, `Variant` and
CSS `AST` nodes.

Right now we use `structuredClone`, which works, but it is a generic
solution. However, we do know the exact structure of these AST nodes, so
we can write specialized clone functions that are much faster.

## Test plan

1. All the tests still pass with this change
2. The performance is better:

```
  cloneCandidate - src/candidate.bench.ts > Candidate cloning
    1.72x faster than cloneCandidate (spread)
    74.03x faster than structuredClone

  cloneAstNode() - src/ast.bench.ts > Cloning AST nodes
    1.15x faster than cloneAstNode (with spread)
    33.54x faster than structuredClone()
```

Ready for review, but should be merged after #19059
2025-10-07 10:46:37 +00:00
Robin Malfait
b77971f754
Introduce canonicalizeCandidates on the internal Design System (#19059)
This PR introduces a new `canonicalizeCandidates` function on the
internal Design System.

The big motivation to moving this to the core `tailwindcss` package is
that we can use this in various places:

- The Raycast extension
- The VS Code extension / language server
- 3rd party tools that use the Tailwind CSS design system APIs

> This PR looks very big, but **I think it's best to go over the changes
commit by commit**. Basically all of these steps already existed in the
upgrade tool, but are now moved to our core `tailwindcss` package.

Here is a list of all the changes:
- Added a new `canonicalizeCandidates` function to the design system
- Moved various migration steps to the core package. I inlined them in
the same file and because of that I noticed a specific pattern (more on
this later).
- Moved `printCandidate` tests to the `tailwindcss` package
- Setup tests for `canonicalizeCandidates` based on the existing tests
in the upgrade tool.

I noticed that all the migrations followed a specific pattern:

1. Parse the raw candidate into a `Candidate[]` AST
2. In a loop, try to migrate the `Candidate` to a new `Candidate` (this
often handled both the `Candidate` and its `Variant[]`)
3. If something changed, print the new `Candidate` back to a string, and
pass it to the next migration step.

While this makes sense in isolation, we are doing a lot of repeated work
by parsing, modifying, and printing the candidate multiple times. This
let me to introduce the `big refactor` commit. This changes the steps
to:

1. Up front, parse the raw candidate into a `Candidate[]` _once_.
2. Strip the variants and the important marker from the candidate. This
means that each migration step only has to deal with the base `utility`
and not care about the variants or the important marker. We can
re-attach these afterwards.
3. Instead of a `rawCandidate: string`, each migration step receives an
actual `Candidate` object (or a `Variant` object).
4. I also split up the migration steps for the `Candidate` and the
`Variant[]`.

All of this means that there is a lot less work that needs to be done.
We can also cache results between migrations. So `[@media_print]:flex`
and `[@media_print]:block` will result in `print:flex` and `print:block`
respectively, but the `[@media_print]` part is only migrated once across
both candidates.

One migration step relied on the `postcss-selector-parser` package to
parse selectors and attribute selectors. I didn't want to introduce a
package just for this, so instead used our own `SelectorParser` in the
migration and wrote a small `AttributeSelectorParser` that can parse the
attribute selector into a little data structure we can work with
instead.

If we want, we can split this PR up into smaller pieces, but since the
biggest chunk is moving existing code around, I think it's fairly doable
to review as long as you go commit by commit.

---

With this new API, we can turn:
```
[
  'bg-red-500',
  'hover:bg-red-500',
  '[@media_print]:bg-red-500',
  'hover:[@media_print]:bg-red-500',
  'bg-red-500/100',
  'hover:bg-red-500/100',
  '[@media_print]:bg-red-500/100',
  'hover:[@media_print]:bg-red-500/100',
  'bg-[var(--color-red-500)]',
  'hover:bg-[var(--color-red-500)]',
  '[@media_print]:bg-[var(--color-red-500)]',
  'hover:[@media_print]:bg-[var(--color-red-500)]',
  'bg-[var(--color-red-500)]/100',
  'hover:bg-[var(--color-red-500)]/100',
  '[@media_print]:bg-[var(--color-red-500)]/100',
  'hover:[@media_print]:bg-[var(--color-red-500)]/100',
  'bg-(--color-red-500)',
  'hover:bg-(--color-red-500)',
  '[@media_print]:bg-(--color-red-500)',
  'hover:[@media_print]:bg-(--color-red-500)',
  'bg-(--color-red-500)/100',
  'hover:bg-(--color-red-500)/100',
  '[@media_print]:bg-(--color-red-500)/100',
  'hover:[@media_print]:bg-(--color-red-500)/100',
  'bg-[color:var(--color-red-500)]',
  'hover:bg-[color:var(--color-red-500)]',
  '[@media_print]:bg-[color:var(--color-red-500)]',
  'hover:[@media_print]:bg-[color:var(--color-red-500)]',
  'bg-[color:var(--color-red-500)]/100',
  'hover:bg-[color:var(--color-red-500)]/100',
  '[@media_print]:bg-[color:var(--color-red-500)]/100',
  'hover:[@media_print]:bg-[color:var(--color-red-500)]/100',
  'bg-(color:--color-red-500)',
  'hover:bg-(color:--color-red-500)',
  '[@media_print]:bg-(color:--color-red-500)',
  'hover:[@media_print]:bg-(color:--color-red-500)',
  'bg-(color:--color-red-500)/100',
  'hover:bg-(color:--color-red-500)/100',
  '[@media_print]:bg-(color:--color-red-500)/100',
  'hover:[@media_print]:bg-(color:--color-red-500)/100',
  '[background-color:var(--color-red-500)]',
  'hover:[background-color:var(--color-red-500)]',
  '[@media_print]:[background-color:var(--color-red-500)]',
  'hover:[@media_print]:[background-color:var(--color-red-500)]',
  '[background-color:var(--color-red-500)]/100',
  'hover:[background-color:var(--color-red-500)]/100',
  '[@media_print]:[background-color:var(--color-red-500)]/100',
  'hover:[@media_print]:[background-color:var(--color-red-500)]/100'
]
```

Into their canonicalized form:
```
[
  'bg-red-500',
  'hover:bg-red-500',
  'print:bg-red-500',
  'hover:print:bg-red-500'
]
```

The list is also unique, so we won't end up with `bg-red-500 bg-red-500`
twice.

While the canonicalization itself is fairly fast, we still pay a **~1s**
startup cost for some migrations (once, and cached for the entire
lifetime of the design system). I would like to keep improving the
performance and the kinds of migrations we do, but I think this is a
good start.

The cost we pay is for:

1. Generating a full list of all possible utilities based on the
`getClassList` suggestions API.
2. Generating a full list of all possible variants.

The canonicalization step for this list takes **~2.9ms** on my machine.

Just for fun, if you use the `getClassList` API for intellisense that
generates all the suggestions and their modifiers, you get a list of
**263788** classes. If you canonicalize all of these, it takes
**~500ms** in total. So roughly **~1.9μs** per candidate.

This new API doesn't result in a performance difference for normal
Tailwind CSS builds.

The other potential concern is file size of the package. The generated
`tailwindcss.tgz` file changed like this:
```diff
- 684652 bytes (684.65 kB)
+ 749169 bytes (749.17 kB)
```
So the package increased by ~65 kB which I don't think is the end of the
world, but it is important for the `@tailwindcss/browser` build which we
don't want to grow unnecessarily. For this reason we remove some of the
code for the design system conditionally such that you don't pay this
cost in an environment where you will never need this API.

The `@tailwindcss/browser` build looks like this:
```shell
`dist/index.global.js 255.14 KB` (main)
`dist/index.global.js 272.61 KB` (before this change)
`dist/index.global.js 252.83 KB` (after this change, even smaller than on `main`)
```
2025-10-07 12:41:12 +02:00
Robin Malfait
73628f69f3
Fix Safari devtools issues because of nested @supports at-rules without normal rule (#19069)
This PR fixes a weird Safari rendering bug in the devtools. This seems
to be happening when using `@supports`, especially nested `@supports`
at-rules.

The issue is that our color-mix fallback generates declarations directly
in `@supports` at-rules which causes the weird rendering bug in Safari.

Adding this intermediate `&` rule seems to fix the issue.

This is a workaround for a browser bug, but the additional 3 characters
shouldn't be the end of the world.

## Test plan

1. Updated the tests with the new `& { }` intermediate rule
2. Other tests still pass as expected

| Before | After |
| --- | --- |
| <img width="450" height="549" alt="image"
src="https://github.com/user-attachments/assets/4b51fb93-8073-4414-8139-dec75e6bc086"
/> | <img width="448" height="548" alt="image"
src="https://github.com/user-attachments/assets/1016af67-c1eb-43dc-9554-158e7e2264c4"
/> |

Fixes: #19065

[ci-all]
2025-10-06 20:04:40 +02:00
Jordan Pittman
b67cbcf6cc
Prepare v4.1.14 release (#19037)
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-10-01 11:48:02 -04:00
depfu[bot]
cc8fefc154 Update magic-string to version 0.30.19 2025-09-26 19:12:26 +00:00
depfu[bot]
210575a6a5
Update dedent 1.6.0 → 1.7.0 (minor) (#19010)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ dedent (1.6.0 → 1.7.0) · [Repo](https://github.com/dmnd/dedent)
· [Changelog](https://github.com/dmnd/dedent/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/dmnd/dedent/releases/tag/v1.7.0">1.7.0</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>docs: cleaned up README.md badges by <a
href="https://bounce.depfu.com/github.com/JoshuaKGoldberg">@JoshuaKGoldberg</a>
in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/100">#100</a>
</li>
<li>feat: add alignValues option by <a
href="https://bounce.depfu.com/github.com/PaperStrike">@PaperStrike</a>
in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/102">#102</a>
</li>
<li>1.7.0 by <a
href="https://bounce.depfu.com/github.com/JoshuaKGoldberg">@JoshuaKGoldberg</a>
in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/103">#103</a>
</li>
</ul>
<h2 dir="auto">New Contributors</h2>
<ul dir="auto">
<li>
<a
href="https://bounce.depfu.com/github.com/PaperStrike">@PaperStrike</a>
made their first contribution in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/102">#102</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/compare/v1.6.0...v1.7.0"><tt>v1.6.0...v1.7.0</tt></a></p></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="ab2ce25762...dd15cf5836">See
the full diff on Github</a>. The new version differs by 3 commits:</p>
<ul>
<li><a
href="dd15cf5836"><code>1.7.0
(#103)</code></a></li>
<li><a
href="304d0fc795"><code>feat:
add alignValues option (#102)</code></a></li>
<li><a
href="aab442c691"><code>docs:
cleaned up README.md badges (#100)</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>
2025-09-26 14:42:26 -04:00
Jordan Pittman
c6e0a55d36
Ensure files with only @theme produce no output when built (#18979)
Closes #18978
2025-09-22 11:59:02 -04:00
Robin Malfait
9a5bae2038
Revert "Update all pnpm dependencies (2025-09-20) (#18971)"
This reverts commit 42eadfd1ee.
2025-09-22 11:34:40 +02:00
depfu[bot]
42eadfd1ee
Update all pnpm dependencies (2025-09-20) (#18971)
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?

✳️ eslint (9.33.0 → 9.35.0, minor) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) ·
[Release](https://github.com/eslint/eslint/releases/tag/v9.35.0) ·
[Diff](a90d7c4fe5...8401101d1e)

✳️ eslint-config-next (15.4.7 → 15.5.2, minor)

✳️ magic-string (0.30.18 → 0.30.19, minor) ·
[Repo](https://github.com/rich-harris/magic-string) ·
[Changelog](https://github.com/Rich-Harris/magic-string/blob/master/CHANGELOG.md)
·
[Diff](0005025c18...bdef7d5ab5)

✳️ @types/bun (1.2.20 → 1.2.21, patch) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)

✳️ @types/react (19.1.9 → 19.1.13, patch) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)

✳️ @types/react-dom (19.1.7 → 19.1.9, patch) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)

✳️ @types/semver (7.7.0 → 7.7.1, patch) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)

✳️ @vitejs/plugin-react (5.0.0 → 5.0.2, patch) ·
[Repo](https://github.com/vitejs/vite-plugin-react) ·
[Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)

✳️ bun (1.2.20 → 1.2.21, patch) · [Repo](https://github.com/oven-sh/bun)

✳️ turbo (2.5.4 → 2.5.6, patch) ·
[Repo](https://github.com/turborepo/turbo)




---
![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>
2025-09-22 11:11:39 +02:00
Robin Malfait
c2aab49c77
Bump Prettier (#18960)
This PR bumps prettier and solves one of our `- *` formatting issues.
Not all, but a few!
2025-09-18 11:17:29 +02: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
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
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
hustrust
c318329a1e
chore: remove redundant words (#18853) 2025-09-02 08:13:15 +00:00
Robin Malfait
ddc84b079b
update test after prettier change 2025-09-01 11:57:54 +02:00
Robin Malfait
f1331a857a
run prettier 2025-09-01 11:42:37 +02:00
okonomi
e5513b6c75
Fix missing code block delimiters in comment blocks (#18837)
## Summary

I fixed some code blocks inside comment blocks that were missing
delimiters.
2025-09-01 09:36:43 +00: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
depfu[bot]
1602e7866d
Update magic-string 0.30.17 → 0.30.18 (minor) (#18821)
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?




#### ✳️ magic-string (0.30.17 → 0.30.18) ·
[Repo](https://github.com/rich-harris/magic-string) ·
[Changelog](https://github.com/Rich-Harris/magic-string/blob/master/CHANGELOG.md)




<details>
<summary>Commits</summary>
<p><a
href="5ce04aa19d...0005025c18">See
the full diff on Github</a>. The new version differs by 5 commits:</p>
<ul>
<li><a
href="0005025c18"><code>chore:
release v0.30.18</code></a></li>
<li><a
href="376bafcb30"><code>chore:
update package.json meta</code></a></li>
<li><a
href="e59c925eb8"><code>chore:
update deps</code></a></li>
<li><a
href="0fd6253e3e"><code>fix:
prevent infinite loop on empty input (#302)</code></a></li>
<li><a
href="a8ee7b79c0"><code>chore:
update eslint config</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>
2025-08-29 14:22:19 +00: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
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