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
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]`.
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.
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.
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`.
This PR effectively reverts #17929.
The bug in npm that required it was fixed a couple of months ago and
with recent changes to pnpm that requires manually approving all
postinstall scripts, this is creating some unnecessary noise.
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
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
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
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>
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
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
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]
### 1. Handling React className migration
The PR fixes an issue when migrating React components to tailwind v4
with the migration tool, that the first class after `className="` is
ignored.
For example, when migrating
```JSX
<div className="shadow"></div>
```
`shadow` will not be migrated to `shadow-sm` .
This is because in `is-safe-migration.ts`, it tests the line before
candidate with regex `/(?<!:?class)=['"]$/`. This basically skips the
migration for anything like `foo="shadow"`, with only exception for Vue
(eg. `class="shadow"`).
The PR changes the regex from
```regex
/(?<!:?class)=['"]$/
````
to
```regex
/(?<!:?class|className)=['"]$/
```
which essentially adds a new exception specifically for React's
`className="shadow"` case.
### 2. Removing redundant rules
Besides, I found that several other rules in
`CONDITIONAL_TEMPLATE_SYNTAX` being redundant since they are already
covered by the rule above, so I removed them. If we prefer the previous
explicit approach, I can revert it.
## Test plan
<!--
Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.
-->
Tests added for both the Vue and React classes to prevent false negative
cases.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR fixes an issue where sometimes people try to run the upgrade
tool, reset the changes and then try again.
If this happens, then the `package.json` and/or your lock file will
point to the old Tailwind CSS v3 version, but the actual installed
version will be v4.
This will also cause the upgrade tool to now upgrade from v4 to v4,
which is not what most people want if they were trying to upgrade from
v3 to v4. This in turn will cause some issues because now we won't try
to migrate the config file, or v3-specific classes that also exist in v4
but are only safe to upgrade from v3 to v4.
This PR uses `npm ls tailwindcss` to determine the actual installed
version. This command already errors if there is a mismatch between the
installed version and the version in `package.json` or the lock file.
This also happens to work in pnpm and bun projects (added integration
tests for these).
If for whatever reason we can't determine the expected version, we fall
back to the old behavior of just upgrading. In this scenario, the
changes introduced in
https://github.com/tailwindlabs/tailwindcss/pull/19026 will at least
give you a hint of what version was actually installed.
### Test plan
1. Tested it in a v3 project where I performed the following steps:
1. Run the upgrade tool in full (`npx tailwindcss-upgrade`)
2. Reset the changes (`git reset --hard && git clean -df`)
1. Run the upgrade tool again
This resulted in the following output: <img width="1059" height="683"
alt="image"
src="https://github.com/user-attachments/assets/1d2ea2d1-b602-4631-958f-cc21eb8a633f"
/>
2. Added some integration tests to make sure this also works in pnpm,
bun and normal npm projects.
[ci-all]
This PR adds support for Maud templates in Rust.
We already had some pre-processing for Rust but for Leptos `class:`
syntax. This PR now added a dedicated Rust pre-processor that handles
Leptos and Maud syntax.
We only start pre-processing Maud templates if the Rust file includes
the `html!` macro.
## Test plan
Looking at the extractor, you can see that we now do extract the proper
classes in Maud templates:
<img width="1076" height="1856" alt="image"
src="https://github.com/user-attachments/assets/e649e1de-289e-466f-8fab-44a938a47dd5"
/>
Fixes: #18984
## Summary
In `@tailwindcss/vite` 's `package.json`, change the `exports` key from
`include` to `default` since there is no `require` case.
Ran into an issue using the `tsx` package to run a script that has a
sub-dependency that imports from `@tailwindcss/vite`, where `tsx`
converts things to cjs to run, and since there is no `require` case for
this package, it can't find the file. Changing to `default` covers the
cases for both `import` and `require`.
## Test plan
No testing needed. Functionality is the same.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
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
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" })
```
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>
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>
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.
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'
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
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
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>
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:
| | `-: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:
| | `-: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>
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>
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.
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
## 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>
This PR is a follow up of #18815 and #18816, but this time let's migrate
the `supports` theme keys.
Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
content: ['./src/**/*.html'],
theme: {
extend: {
supports: {
// Automatically handled by bare values (using CSS variable as the value)
foo: 'foo: var(--foo)', // parentheses are optional
bar: '(bar: var(--bar))',
// Not automatically handled because names differ
baz: 'qux: var(--foo)',
// ^^^ ^^^ ← different names
// Custom
grid: 'display: grid',
},
},
},
}
```
Then we would generate the following Tailwind CSS v4 CSS:
```css
@custom-variant supports-baz {
@supports (qux: var(--foo)) {
@slot;
}
}
@custom-variant supports-grid {
@supports (display: grid) {
@slot;
}
}
```
Notice how we didn't generate a custom variant for `data-foo` or
`data-bar` because those are automatically handled by bare values.
I also went with the longer form of `@custom-variant`, we could use the
single selector approach, but that felt less clear to me.
```css
@custom-variant supports-baz (@supports (qux: var(--foo)));
@custom-variant supports-grid (@supports (display: grid));
```
---------
Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
This PR is similar to and a follow up of #18815, but this time to
migrate the `data` theme keys.
Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
content: ['./src/**/*.html'],
theme: {
extend: {
data: {
// Automatically handled by bare values
foo: 'foo',
// ^^^ ^^^ ← same names
// Not automatically handled by bare values
bar: 'baz',
// ^^^ ^^^ ← different names
// Completely custom
checked: 'ui~="checked"',
},
},
},
}
```
Then we would generate the following Tailwind CSS v4 CSS:
```css
@custom-variant data-bar (&[data-baz]);
@custom-variant data-checked (&[data-ui~="checked"]);
```
Notice how we didn't generate a custom variant for `data-foo` because
those are automatically handled by bare values.
This PR migrates `aria` theme keys when migrating from Tailwind CSS v3
to v4.
While working on improving some of the error messages to get more
insights into why migrating the JS file changed
(https://github.com/tailwindlabs/tailwindcss/pull/18808), I ran into an
issue where I couldn't think of a good comment to why `aria` theme keys
were not being migrated. (Internally we have `aria` "blocked").
So instead of figuring out a good error message..., I just went ahead
and added the migration for `aria` theme keys.
Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
content: ['./src/**/*.html'],
theme: {
extend: {
aria: {
// Built-in (not really, but visible because of intellisense)
busy: 'busy="true"',
// Automatically handled by bare values
foo: 'foo="true"',
// ^^^ ^^^ ← same names
// Not automatically handled by bare values because names differ
bar: 'baz="true"',
// ^^^ ^^^ ← different names
// Completely custom
asc: 'sort="ascending"',
desc: 'sort="descending"',
},
},
},
}
```
Then we would generate the following Tailwind CSS v4 CSS:
```css
@custom-variant aria-bar (&[aria-baz="true"]);
@custom-variant aria-asc (&[aria-sort="ascending"]);
@custom-variant aria-desc (&[aria-sort="descending"]);
```
Notice how we didn't generate a custom variant for `aria-busy` or
`aria-foo` because those are automatically handled by bare values.
We could also emit a comment near the CSS to warn about the fact that
`@custom-variant` will always be sorted _after_ any other built-in
variants.
This could result in slightly different behavior, or different order of
classes when using `prettier-plugin-tailwindcss`.
I don't know how important this is, because before this PR we would just
use `@config './tailwind.config.js';`.
Edit: when using the `@config` we override `aria` and extend it, which
means that it would be in the expected order 🤔
---------
Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
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.
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;
}
}
}
```
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