This PR will optimize and simplify the candidates when printing the
candidate again after running codemods.
When we parse a candidate, we will add spaces around operators, for
example `p-[calc(1px+1px)]]` will internally be handled as `calc(1px +
1px)`. Before this change, we would re-print this as:
`p-[calc(1px_+_1px)]`.
This PR changes that by simplifying the candidate again so that the
output is `p-[calc(1px+1px)]`. In addition, if _you_ wrote
`p-[calc(1px_+_1px)]` then we will also simplify it to the concise form
`p-[calc(1px_+_1px)]`.
Some examples:
Input:
```html
<div class="[p]:flex"></div>
<div class="[&:is(p)]:flex"></div>
<div class="has-[p]:flex"></div>
<div class="px-[theme(spacing.4)-1px]"></div>
```
Output before:
```html
<div class="[&:is(p)]:flex"></div>
<div class="[&:is(p)]:flex"></div>
<div class="has-[&:is(p)]:flex"></div>
<div class="px-[var(--spacing-4)_-_1px]"></div>
```
Output after:
```html
<div class="[p]:flex"></div>
<div class="[p]:flex"></div>
<div class="has-[p]:flex"></div>
<div class="px-[var(--spacing-4)-1px]"></div>
```
---
This is alternative implementation to #14717 and #14718Closes: #14717Closes: #14718
This PR fixes an issue where spaces in a selector generated invalid CSS.
Lightning CSS will throw those incorrect lines of CSS out, but if you
are in an environment where Lightning CSS doesn't run then invalid CSS
is generated.
Given this input:
```html
data-[foo_=_"true"]:flex
```
This will be generated:
```css
.data-\[foo_\=_\"true\"\]\:flex[data-foo=""true] {
display: flex;
}
```
With this PR in place, the generated CSS will now be:
```css
.data-\[foo_\=_\"true\"\]\:flex[data-foo="true"] {
display: flex;
}
```
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
With the changes in #14672, it now becomes trivial to actually resolve
the config (while still retaining the reset behavior). This means that
we can now convert JS configs that use _functions_, e.g.:
```ts
import { type Config } from 'tailwindcss'
export default {
theme: {
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
},
} satisfies Config
```
This becomes:
```css
@import 'tailwindcss';
@theme {
--color-gray-50: #fafafa;
--color-gray-100: #f5f5f5;
--color-gray-200: #e5e5e5;
--color-gray-300: #d4d4d4;
--color-gray-400: #a3a3a3;
--color-gray-500: #737373;
--color-gray-600: #525252;
--color-gray-700: #404040;
--color-gray-800: #262626;
--color-gray-900: #171717;
--color-gray-950: #0a0a0a;
}
```
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR updates all of our gradient utilities to interpolate using OKLCH
by default instead of sRGB. This results in a smoother transition
between colors that preserves saturation throughout the gradient, rather
than hitting the dreaded dull gray zone in between your color stops.
Here are a few examples comparing sRGB (top) to OKLCH (bottom):
<img width="736" alt="image"
src="https://github.com/user-attachments/assets/57a158b6-a3a2-4eda-813e-1b596c7d4b3a">
We only apply a default interpolation mode when _not_ using arbitrary
values with the gradient utility.
Simplified but clear:
```css
.bg-linear-to-r {
background-image: linear-gradient(to right in oklch, var(--gradient-color-stops));
}
.bg-linear-[to_right] {
background-image: linear-gradient(to right, var(--gradient-color-stops));
}
.bg-linear-[to_right_in_hsl] {
background-image: linear-gradient(to right in hsl, var(--gradient-color-stops));
}
```
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR adds support for linear gradient angles as bare values, like
this:
```
bg-linear-45 => linear-gradient(45deg, …)
```
We already support this for [conic
gradients](https://github.com/tailwindlabs/tailwindcss/pull/14467), so
this makes these utilities more consistent.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Prior to this PR we were providing fallback values for certain CSS
variables in our gradient utilities that just weren't necessary and
didn't do anything.
For example `bg-linear-to-r` was generating this:
```css
.bg-linear-to-r {
--tw-gradient-position: to right;
background-image: linear-gradient(
var(--tw-gradient-stops, to right)
);
}
```
…but `background-image: linear-gradient(to right)` is not valid CSS and
is thrown out by the browser.
This PR removes these fallback values entirely since there is nothing
sensible to fall back to anyways — you need to combine these utilities
with the `from-*`/`to-*` utilities or provide the complete gradient as
an arbitrary value for things to make sense.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR updates all of the colors in our default theme to use OKLCH
instead of RGB and increases the overall chroma to take advantage of the
wider color gamut.
Just a first draft based on @danhollick's initial work — expecting these
will be further refined before a stable release as we continue to test
them.
<img width="628" alt="image"
src="https://github.com/user-attachments/assets/2de1bfca-fddd-47f9-b609-39f26abdee41">
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR adds a codemod to convert `theme(…)` calls to `var(…)` calls. If
we can't safely do this, then we try to convert the `theme(…)` syntax
(dot notation) to the modern `theme(…)` syntax (with CSS variable-like
syntax).
### Let's look at some examples:
**Simple example:**
Input:
```html
<div class="bg-[theme(colors.red.500)]"></div>
```
Output:
```html
<div class="bg-[var(--color-red-500)]"></div>
```
---
**With fallback:**
Input:
```html
<div class="bg-[theme(colors.red.500,theme(colors.blue.500))]"></div>
```
Output:
```html
<div class="bg-[var(--color-red-500,var(--color-blue-500))]"></div>
```
---
**With modifiers:**
Input:
```html
<div class="bg-[theme(colors.red.500/75%)]"></div>
```
Output:
```html
<div class="bg-[var(--color-red-500)]/75"></div>
```
We can special case this, because if you are using that modifier syntax
we _assume_ it's being used in a `theme(…)` call referencing a color.
This means that we can also convert it to a modifier on the actual
candidate.
---
**With modifier, if a modifier is already present:**
Input:
```html
<div class="bg-[theme(colors.red.500/75%)]/50"></div>
```
Output:
```html
<div class="bg-[theme(--color-red-500/75%)]/50"></div>
```
In this case we can't use the `var(…)` syntax because that requires us
to move the opacity modifier to the candidate itself. In this case we
could use math to figure out the expected modifier, but that might be
too confusing. Instead, we convert to the modern `theme(…)` syntax.
---
**Multiple `theme(…)` calls with modifiers:**
Input:
```html
<div class="bg-[theme(colors.red.500/75%,theme(colors.blue.500/50%))]"></div>
```
Output:
```html
<div class="bg-[theme(--color-red-500/75%,theme(--color-blue-500/50%))]"></div>
```
In this case we can't convert to `var(…)` syntax because then we lose
the opacity modifier. We also can't move the opacity modifier to the
candidate itself e.g.: `/50` because we have 2 different variables to
worry about.
In this situation we convert to the modern `theme(…)` syntax itself.
---
**Inside variants:**
Input:
```html
<div class="max-[theme(spacing.20)]:flex"></div>
```
Output:
```html
<div class="max-[theme(--spacing-20)]:flex"></div>
```
Unfortunately we can't convert to `var(…)` syntax reliably because in
some cases (like the one above) the value will be used inside of an
`@media (…)` query and CSS doesn't support that at the time of writing
this PR.
So to be safe, we will not try to convert `theme(…)` to `var(…)` in
variants, but we will only upgrade the `theme(…)` call itself to modern
syntax.
Imagine the following setup:
```css
/* src/input.css */
@import "tailwindcss";
@config "../tailwind.config.ts";
@theme {
--color-red-500: #ef4444;
}
```
```ts
/* tailwind.config.ts */
export default {
theme: {
colors: {
red: {
600: '#dc2626'
}
},
extend: {
colors: {
400: '#f87171'
}
}
}
}
```
Since the theme object in the JS config contains `colors` in the
non-`extends` block, you would expect this to _not pull in all the
default colors imported via `@import "tailwindcss";`_. This, however,
wasn't the case right now since all theme options were purely _additive_
to the CSS.
This PR makes it so that non-`extend` theme keys _overwrite default CSS
theme values_. The emphasis is on `default` here since you still want to
be able to overwrite your options via `@theme {}` in user space.
This now generates the same CSS that our upgrade codemods would also
generate as this would apply the new CSS right after the `@import
"tailwindcss";` rule resulting in:
```css
@import "tailwindcss";
@theme {
--color-*: initial;
--color-red-400: #f87171;
--color-red-600: #dc2626;
}
@theme {
--color-red-500: #ef4444;
}
```
## Keyframes
This PR also adds a new core API to unset keyframes the same way. We
previously had no option of doing that but while working on the above
codemods we noticed that keyframes should behave the same way:
```css
@import "tailwindcss";
@theme {
--keyframes-*: initial;
@keyframes spin {
to {
transform: rotate(361deg);
}
}
}
```
To do this, the keyframes bookeeping was moved from the main Tailwind
CSS v4 file into the `Theme` class.
_I’m not sure super of the API yet but we would need a way for the
codemods to behave the same as out interop layer here. Option B is that
we don't reset keyframes the same way we reset other theme variables_.
This PR makes sure that you cannot use `@apply` inside `@keyframes`.
While some utilities can be used in `@keyframes`, the moment you
introduce a variant, that's not going to work anymore because they need
to operate on selectors which `@keyframes` don't have.
This PR now removes all usages of `@apply` in `@keyframes`.
This PR fixes an issue where bare values in the `font-stretch` utility
such as `font-stretch-50.5%` compiled. But for bare values, we want
these values to be positive integers only so that we don't have too many
special characters.
If you want to use `50.5%`, you can still use `font-stretch-[50.5%]` as
a utility.
Inside the `theme(…)` function, we can use the `/` character for
applying an opacity. For example `theme(colors.red.500 / 50%)` will
apply a 50% opacity to the `colors.red.500` value.
However, if you used a variable instead of the hardcoded `50%` value,
then this was not parsed correctly. E.g.: `theme(colors.red.500 /
var(--opacity))`
_If_ we have this exact syntax (with the spaces), then it parses, but
some information is lost:
```html
<div class="bg-[theme(colors.red.500_/_var(--opacity))]"></div>
```
Results in:
```css
.bg-\[theme\(colors\.red\.500_\/_var\(--opacity\)\)\] {
background-color: color-mix(in srgb, #ef4444 calc(var * 100%), transparent);
}
```
Notice that the `var(--opacity)` is just parsed as `var`, and the
`--opacity` is lost.
Additionally, if we drop the spaces, then it doesn't parse at all:
```html
<div class="bg-[theme(colors.red.500/var(--opacity))]"></div>
```
Results in:
```css
```
This means that we have to handle 2 issues to make this work:
1. We have to properly handle the `/` character as a proper separator.
2. If we have sub-functions, we have to make sure to print them in full
(instead of only the very first node (`var` in this case)).
Quick follow-up to #14659 base don @thecrypticace's idea:
- This behavior is no longer added to the types of the Plugin API to be
consistent with v3
- When the plugin argument is used as a function, we now warn the first
time
Something we noticed while testing the codemods on one of our codebases
is that the callback passed to the `theme` function properties doesn't
only expose some properties like `colors`, but it's also a function
itself.
```ts
/** @type {import('tailwindcss').Config} */
export default {
theme: {
extend: {
colors: (theme) => {
// The `theme` is a theme function _and_ the object...
console.log(theme('spacing.2'), theme.colors.red['500'])
return {}
},
},
},
plugins: [],
}
```
E.g.: https://play.tailwindcss.com/eV7Jgv17X1?file=config
---
h/t to @RobinMalfait for the issue description
While working on some fixes for #14639 I noticed that the following v3
configuration file would not load properly in v4:
```ts
import { type Config } from 'tailwindcss'
export default {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
} satisfies Config
```
The reason for this is that we did not pass the `colors` property to the
callback function. Since we have colors available now, we can easily add
it.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
A few theme keys have changed in v4 relative to v3:
- `screens` -> `--breakpoint-*`
- `colors` -> `--color-*`
- `animation` -> `--animate-*`
- `borderRadius` -> `--radius-*`
- `boxShadow` -> `--shadow-*`
When using the `theme()` function we wouldn't pick up values from the
CSS for some of these. Likewise, when loading a v3 config not all of
these would be pushed back into the CSS theme and they should've been.
This PR addresses both of these problems.
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
We forgot to pass `options` from `addComponents` to `addUtilities` and
from `matchComponents` to `matchUtilities`.
This didn't affect anything using addComponents but anything that used
`matchComponents` wouldn't have worked 😬
Discovered `matchUtilities(…)` wasn't populating intellisense on v4 when
working on Tailwind Play earlier today. This PR fixes this so plugins
using matchUtilities also have intellisense suggestions.
We currently have three different implementations of the
`resolveThemeValue()` Theme method:
1. The _core_ version which only resolves based on the exact CSS
variable name. This is the v4-only version.
2. A _compat light_ version which attempts to translate a dot-notation
keypath to a CSS variable name.
3. A _full compat_ version which resolves the legacy theme object which
is used whenever a `@plugin` or `@config` is added.
Unfortunately, there are some issues with this approach that we've
encountered when testing the CSS `theme()` function while upgrading
Tailwind Templates to v4 using our upgrading toolkit.
1. The _compat light_ resolver was trying to convert `theme(spacing.1)`
to tuple syntax. Tuples require a nested property access, though, and
instead this should be convert to `theme(--spacing-1)`.
2. We currently only load _full compat_ version if `@plugin` or
`@config` directives are used. It is possible, though, that we need the
full compat mapping in other cases as well. One example we encountered
is `theme(fontWeight.semibold)` which maps to a dictionary in the
default theme that that we do not have an equivalent for in v4
variables.
To fix both issues, we decided to remove the _compat light_ version of
the `theme()` function in favor of adding this behavior to an upgrade
codemod. Instead, the second layer now only decides wether it can use
the _core_ version or wether it needs to upgrade to the _full compat_
version. Since typos would trigger a _hard error_, we do not think this
has unintended performance consequences if someone wants to use the
_core_ version only (they would get an error regardless which they need
to fix in order to continue).
Fixes an issue reported by the React Aria Components team here:
https://github.com/adobe/react-spectrum/issues/7160
Basically `hidden="until-found"` behaves very differently than `hidden`
and doesn't actually use `display: none`, so we don't want to apply the
behavior we apply for the regular `hidden` attribute.
This PR fixes two issues we found when testing the candidate codemodes:
1. Sometimes, core would emit the same candidate twice. This would
result into rewriting a range multiple times, without realizing that
this change might already be applied, causing it to swallow or duplicate
some bytes.
2. The codemods were mutating the `Candidate` object, however since the
`Candidate` parsing is _cached_ in core, it would sometimes return the
same instance. This is an issue especially since we monkey patch the
prefix to `null` when migrating prefixed candidates. This means that a
candidate would be cached that would be _invalid relative to the real
design system_. We fixed this by making sure the mutations would only be
applied to clones of the `Candidate` and I changed the `DesignSystem`
API to return `ReadOnly<T>` versions of these candidates. A better
solution would maybe be to disable the cache at all but this requires
broader changes in Core.
This PR makes the internal `@at-root` API private. Before this PR you
could use `@at-root` in your own CSS, which means that it was part of
the public API. If you (accidentally) used it in variants, you could
generate CSS that was completely broken.
This now introduces a new private `AtRoot` node (similar to the recently
introduced `Context` node) and can only be constructed within the
framework.
In order to properly migrate your Tailwind CSS v3 project to v4, we need
access to the JavaScript configuration object. This was previously only
required for template migrations, but in this PR we're making it so that
this is also a prerequisite of the CSS migrations. This is because some
migrations, like `@apply`, also need to convert candidates that to the
v4 syntax and we need the full config in order to properly validate
them.
In addition to requiring a JS config, we also now attempt to
automatically find the right configuration file inside the current
working directory. This is now matching the behavior of the Tailwind CSS
v3 CLI where it will find the config automatically if it's in the
current directory and called `tailwind.conf.js`.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR is a very small improvement. We started pretty printing the
generated CSS (proper indentation) a while ago, so that we can use the
output as-is for intellisense (on hover).
The other day I noticed that when you use `!important` that we attach it
directly to the declaration. Not the end of the world, but this PR
injects a little space to make sure that the `!important` is separated
from the value which makes it a little easier to read and looks more
like what you would write by hand.
Before:
```css
.flex\! {
display: flex!important;
}
```
After:
```css
.flex\! {
display: flex !important;
}
```
In v3 it was possible to import `*.js` variants of compat files, e.g.
`tailwindcss/plugin.js` in addition to `tailwindcss/plugin`. We need to
properly map the exports to support this.
When a prefix is set in a stylesheet and we found a candidate that is
equal to the prefix we would crash:
```css
@import "tailwindcss" prefix(tomato);
```
```js
console.log("tomato")
```
This PR fixes this case by ensuring that we have something that looks
like a variant before considering a prefix.
This PR allows modifying utility output by wrapping all utility
declarations in a custom selector or marking all utility declarations as
`!important`. This is the v4 equivalent to the `important` option in the
`tailwind.config.js` file.
## Mark all utility declarations as `!important`
To add `!important` to all utility declarations, you add an `important`
flag after the `@import` statement for `tailwindcss` or
`tailwindcss/utilities`:
```css
/** Importing `tailwindcss` */
@import "tailwindcss" important;
/** Importing the utilities directly */
@import "tailwindcss/utilities" important;
```
Example utility output:
```css
.mx-auto {
margin-left: auto !important;
margin-right: auto !important;
}
.text-center {
text-align: center !important;
}
```
This is equivalent to adding `important: true` to the
`tailwind.config.js` file — which is still supported for backwards
compatibility.
## Wrap all utility declarations in a custom selector
To nest all utilities in an `#app` selector you add `selector(#app)`
flag after the `@import` statement for `tailwindcss` or
`tailwindcss/utilities`:
```css
/** Importing `tailwindcss` */
@import "tailwindcss" selector(#app);
/** Importing the utilities directly */
@import "tailwindcss/utilities" selector(#app);
```
Example utility output:
```css
.mx-auto {
#app & {
margin-left: auto;
margin-right: auto;
}
}
.text-center {
#app & {
text-align: center;
}
}
```
This is equivalent to adding `important: "#app"` to the
`tailwind.config.js` file — which is still supported for backwards
compatibility.
**This _does not_ bring back support for the `respectImportant` flag in
`addUtilities` / `matchUtilities`.**
Part-of #14558
After handling `@import` of stylesheets in core, we can no longer gate
out features based on finding a specific sequence in the input CSS, as
this string will not contain contents from other stylesheets.
So, consider the following input CSS:
```css
@import "tailwindcsss";
@import "./styles.css";
```
We can't opt-out of the traversal to search for `@apply` based on it
because it, of course, does not contain the contents of `./styles.css`
yet.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Replaces #11271 — the merge conflicts are killing me and it's way easier
to just do it again (added you as a co-author though @lukewarlow so
you're still credited!)
This PR adds the following new utilities for the `color-scheme`
property:
| Class | CSS |
| ------------------- | -------------------------- |
| `scheme-normal` | `color-scheme: normal` |
| `scheme-dark` | `color-scheme: dark` |
| `scheme-light` | `color-scheme: light` |
| `scheme-light-dark` | `color-scheme: light dark` |
| `scheme-only-dark` | `color-scheme: dark only` |
| `scheme-only-light` | `color-scheme: light only` |
Went with `scheme-*` for the utilities because there are currently no
other CSS properties with the word `scheme` in them and
`scheme-light-dark` is going to be a common value which is three words
already even with the shortened property name.
I considered setting `color-scheme: light dark` by default as part of
Preflight for this PR but decided against it, at least for this PR. I
think that could still be a useful default but we should think about it
a bit more because if you're building a light-mode-only site it'll force
you to do some extra work.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Luke Warlow <lwarlow@igalia.com>
I noticed our prefix tests were missing an explicit test for `@import
"tailwindcss" prefix(tw)` so this PR tweaks an existing test to test
that case.
Also noticed that `prefix.test.ts` was in the `compat` folder even
though this is a v4 feature, so moved it out to the root.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
I noticed some more unexpected values being passed through as _bare
values_: Decimal places with zero.
These should not generate CSS but currently does:
- `from-25.%`
- `from-25.0%`
- `from-25.00%`
- etc.
This PR adds support for the `blocklist` config option when using a JS
config file in v4. You can now block certain classes from being
generated at all. This is useful in cases where scanning files sees
things that look like classes but are actually not used. For example, in
paragraphs in a markdown file:
```js
// tailwind.config.js
export default {
blocklist: ['bg-red-500'],
}
```
```html
<!-- index.html -->
<div class="bg-red-500 text-black/75"></div>
```
Output:
```css
.text-black/75 {
color: rgba(0, 0, 0, 0.75);
}
```
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR adds support for requiring a custom prefix on utility classes.
Prefixes work a bit differently in v4 than they did in v3:
- They look like a custom variant: `tw:bg-white`
- It is always first in a utility — even before other variants:
`tw:hover:bg-white`
- It is required on **all** utility classes — even arbitrary properties:
`tw:[color:red]`
- Prefixes also apply to generated CSS variables which will be separated
by a dash: `--tw-color-white: #fff;`
- Only alpha (a-z) characters are allowed in a prefix — so no `#tw#` or
`__` or similar prefixes are allowed
To configure a prefix you can use add `prefix(tw)` to your theme or when
importing Tailwind CSS like so:
```css
/* when importing `tailwindcss` */
@import 'tailwindcss' prefix(tw);
/* when importing the theme separately */
@import 'tailwindcss/theme' prefix(tw);
/* or when using an entirely custom theme */
@theme prefix(tw) {
--color-white: #fff;
--breakpoint-sm: 640px;
/* … */
}
```
This will configure Tailwind CSS to require a prefix on all utility
classes when used in HTML:
```html
<div class="tw:bg-white tw:p-4">
This will have a white background and 4 units of padding.
</div>
<div class="bg-white p-4">
This will not because the prefix is missing.
</div>
```
and when used in CSS via `@apply`:
```css
.my-class {
@apply tw:bg-white tw:p-4;
}
```
Additionally, the prefix will be added to the generated CSS variables.
You **do not** need to prefix the variables in the `@theme` block
yourself — Tailwind CSS handles this automatically.
```css
:root {
--tw-color-white: #fff;
--tw-breakpoint-sm: 640px;
}
```
A prefix is not necessary when using the `theme(…)` function in your CSS
or JS given that plugins will not know what the current prefix is and
must work with or without a prefix:
```css
.my-class {
color: theme(--color-white);
}
```
However, because the variables themselves are prefixed when outputting
the CSS, you **do** need to prefix the variables when using `var(…)` in
your CSS:
```css
.my-class {
color: var(--tw-color-white);
}
```
If you want to customize the prefix itself change `tw` to something
else:
```css
/* my:underline, my:hover:bg-red-500, etc… */
@import 'tailwindcss' prefix(my);
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR adds CSS codemods for migrating existing `@layer utilities` to
`@utility` directives.
This PR has the ability to migrate the following cases:
---
The most basic case is when you want to migrate a simple class to a
utility directive.
Input:
```css
@layer utilities {
.foo {
color: red;
}
.bar {
color: blue;
}
}
```
Output:
```css
@utility foo {
color: red;
}
@utility bar {
color: blue;
}
```
You'll notice that the class `foo` will be used as the utility name, the
declarations (and the rest of the body of the rule) will become the body
of the `@utility` definition.
---
In v3, every class in a selector will become a utility. To correctly
migrate this to `@utility` directives, we have to register each class in
the selector and generate `n` utilities.
We can use nesting syntax, and replace the current class with `&` to
ensure that the final result behaves the same.
Input:
```css
@layer utilities {
.foo .bar .baz {
color: red;
}
}
```
Output:
```css
@utility foo {
& .bar .baz {
color: red;
}
}
@utility bar {
.foo & .baz {
color: red;
}
}
@utility .baz {
.foo .bar & {
color: red;
}
}
```
In this case, it could be that you know that some of them will never be
used as a utility (e.g.: `hover:bar`), but then you can safely remove
them.
---
Even classes inside of `:has(…)` will become a utility. The only
exception to the rule is that we don't do it for `:not(…)`.
Input:
```css
@layer utilities {
.foo .bar:not(.qux):has(.baz) {
display: none;
}
}
```
Output:
```css
@utility foo {
& .bar:not(.qux):has(.baz) {
display: none;
}
}
@utility bar {
.foo &:not(.qux):has(.baz) {
display: none;
}
}
@utility baz {
.foo .bar:not(.qux):has(&) {
display: none;
}
}
```
Notice that there is no `@utility qux` because it was used inside of
`:not(…)`.
---
When classes are nested inside at-rules, then these classes will also
become utilities. However, the `@utility <name>` will be at the top and
the at-rules will live inside of it. If there are multiple classes
inside a shared at-rule, then the at-rule will be duplicated for each
class.
Let's look at an example to make it more clear:
Input:
```css
@layer utilities {
@media (min-width: 640px) {
.foo {
color: red;
}
.bar {
color: blue;
}
@media (min-width: 1024px) {
.baz {
color: green;
}
@media (min-width: 1280px) {
.qux {
color: yellow;
}
}
}
}
}
```
Output:
```css
@utility foo {
@media (min-width: 640px) {
color: red;
}
}
@utility bar {
@media (min-width: 640px) {
color: blue;
}
}
@utility baz {
@media (min-width: 640px) {
@media (min-width: 1024px) {
color: green;
}
}
}
@utility qux {
@media (min-width: 640px) {
@media (min-width: 1024px) {
@media (min-width: 1280px) {
color: yellow;
}
}
}
}
```
---
When classes result in multiple `@utility` directives with the same
name, then the definitions will be merged together.
Input:
```css
@layer utilities {
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
}
```
Intermediate representation:
```css
@utility no-scrollbar {
&::-webkit-scrollbar {
display: none;
}
}
@utility no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
```
Output:
```css
@utility no-scrollbar {
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none
}
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR updates the `hover` variant to only apply when `@media (hover:
hover)` matches.
```diff
.hover\:bg-black {
&:hover {
+ @media (hover: hover) {
background: black;
+ }
}
}
```
This is technically a breaking change because you may have built your
site in a way where some interactions depend on hover (like opening a
dropdown menu), and were relying on the fact that tapping on mobile
triggers hover.
To bring back the old hover behavior, users can override the `hover`
variant in their CSS file back to the simpler implementation:
```css
@import "tailwindcss";
@variant hover (&:hover);
```
I've opted to go with just `@media (hover: hover)` for this because it
seems like the best trade-off between the available options. Using
`(any-hover: hover)` would mean users would get sticky hover states when
tapping on an iPad if they have a mouse or trackpad connected, which
feels wrong to me because in those cases touch is still likely the
primary method of interaction.
Sites built with this feature in mind will be treating hover styles as
progressive enhancement, so it seems better to me that using an iPad
with a mouse would not have hover styles, vs. having sticky hover styles
in the same situation.
Of course users can always override this with whatever they want, so
making this the default isn't locking anyone in to a particular choice.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR is a continuation of #13537.
Currently we reverted the merged changes so that we can get a new alpha
version out without this change.
---
This PR removes automatic `var(…)` injection for arbitrary properties,
values and modifiers.
There are a few properties that use "dashed-ident" values, this means
that you can use `--my-thing` as-is without the `var(…)` around it.
E.g.:
```css
.foo {
/* Notice that these don't have `var(…)` */
view-timeline-name: --timeline-name;
anchor-name: --sidebar;
}
```
This causes issues because we are now injecting a `var(…)` where it's
not needed.
One potential solution is to create a list of properties where dashed
idents can be used. However, they can _also_ use CSS custom properties
that point to another dashed ident.
E.g.:
```css
.foo {
--target: --sidebar;
anchor-name: var(--target);
}
```
A workaround that some people used is adding a `_` in front of the
variable: `mt-[_--my-thing]` this way we don't automatically inject the
`var(…)` around it. This is a workaround and gross.
While the idea of automatic var injection is neat, this causes more
trouble than it's worth. Adding `var(…)` explicitly is better.
A side effect of this is that we can simplify the internals for the
`candidate` data structure because we don't need to track `dashedIdent`
separately anymore.
<!--
👋 Hey, thanks for your interest in contributing to Tailwind!
**Please ask first before starting work on any significant new
features.**
It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.
https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md
-->
This fixes the following issue
https://github.com/tailwindlabs/tailwindcss/issues/14305 by using
`:dir()`
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>