- Avoid cross-device copying in Windows CI by setting the tests dir to
the same drive as the workspace.
- Disable LTO and use a faster linker for the Rust build
Buid: ~3min -> ~2min
Integration Tests: ~8min -> ~3min20s
Fixes#14479.
Back in March we made a change to the `transition-*` utilities that
inlined the values of the `--default-transition-*` variables to fix a
bug where things would break if those variables didn't exist in your
CSS. At the time though we weren't outputting CSS variables as part of
the values of any utilities, for example `bg-red-500` didn't actually
reference the `--color-red-500` variable.
We later changed that but missed this situation, so these variables were
still inlined even though nothing else was.
This PR fixes that and makes things more consistent, so these variables
will be used as expected unless using the `@theme inline` option, like
with everything else.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR adds support for the
[`field-sizing`](https://developer.mozilla.org/en-US/docs/Web/CSS/field-sizing)
property which can be used to fit a text inputs, file inputs, textareas,
and selects to the size of the text rather than some implicit default
width.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
<!--
👋 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
-->
```ts
// error: can't find `@tailwindcss/node` types file
import { compile } from '@tailwindcss/node';
```
This PR complements #14458 by adding new `shadow-initial` and
`inset-shadow-initial` utilities that make it possible to "undo" a
custom shadow color and revert to the default shadow color for the
current shadow size.
For example, in this example the shadow will be red on hover even though
the default color for the `shadow` utility is `rgb(0 0 0 / 5%)`:
```html
<div class="shadow-sm shadow-red-500 hover:shadow">
<!-- … -->
</div>
```
This is usually what you want, but if you actually want `hover:shadow`
to apply its default color, you need to know what the color is and add
it yourself:
```html
<div class="shadow-sm shadow-red-500 hover:shadow hover:shadow-black/5">
<!-- … -->
</div>
```
Using `shadow-initial`, you can achieve the same thing without having to
know what the original color was:
```html
<div class="shadow-sm shadow-red-500 hover:shadow hover:shadow-initial">
<!-- … -->
</div>
```
The `inset-shadow-initial` utility does the same thing for the
`inset-shadow-*` utilities.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR adds new `bg-radial-*` and `bg-conic-*` utilities for radial and
conic gradients. It also adds support for "arbitrary gradients", where
gradient utilities like `bg-linear-*`, `bg-radial-*`, and `bg-conic-*`
can now accept a complete gradient definition as their arbitrary value.
## Radial gradients
Radial gradients are created with the `bg-radial` utility, or the
`bg-radial-[…]` utility, combined with the existing `from-*`, `via-*`,
and `to-*` utilities.
The simple `bg-radial` utility just creates a radial gradient with no
position information, which defaults to `center`:
```
radial-gradient({from}, {via}, {to});
```
If you use the arbitrary value format, whatever you provide as the
arbitrary value is inserted into the first position:
```
radial-gradient({arbitrary value}, {from}, {via}, {to});
```
So a utility like `bg-radial-[at_top_left]` would produce this:
```
radial-gradient(at top left, {from}, {via}, {to});
```
This makes it possible to use some of the `radial-gradient(…)` features
that this PR doesn't add first class support for, like using values like
`circle at center` or providing a specific interpolation color space
like `in hsl longer hue`. We may add explicit APIs for these in the
future, but I'm proposing this PR first since those changes would be
purely additive and none of the decisions here would create any conflict
with those APIs.
## Conic gradients
Conic gradients are created with the `bg-conic`,
`bg-conic-{bareNumber}`, and `bg-conic-[…]` utilities, combined with the
existing `from-*`, `via-*`, and `to-*` utilities.
The `bg-conic` utility creates a conic gradient with no angle, which
defaults to `0deg`:
```
conic-gradient({from}, {via}, {to});
```
The `bg-conic-{bareNumber}` utilities create conic gradients using the
bare number as the angle:
```
conic-gradient(from {bareNumber}deg, {from}, {via}, {to});
```
The `bg-conic-[…]` arbitrary value utilities insert whatever you provide
as the arbitrary value into the first position verbatim:
```
conic-gradient({arbitraryValue}, {from}, {via}, {to});
```
So a utility like `bg-conic-[from_45deg_in_hsl]` would produce this:
```
conic-gradient(from 45deg in hsl, {from}, {via}, {to});
```
Note that the `from` keyword needs to be provided by the user when using
arbitrary values, but not when using bare values.
This makes it possible to use some of the `conic-gradient(…)` features
that this PR doesn't add first class support for, like using values like
`at 0 0` or providing a specific interpolation color space like `in hsl
longer hue`. We may add explicit APIs for these in the future, but I'm
proposing this PR first since those changes would be purely additive and
none of the decisions here would create any conflict with those APIs.
## Arbitrary gradients
Prior to this PR, utilities like `bg-linear-[…]` could only accept
positional information as their arbitrary value, like
`bg-linear-[to_top_right]`. All of the color stops could only be
provided using the `from-*`, `via-*`, and `to-*` utilities.
If you wanted to provide the complete gradient in one class, you needed
to use `bg-[…]` and write out the gradient function yourself:
```html
<div class="bg-[linear-gradient(to_right,var(--color-red-500),var(--color-yellow-400))]">
```
This PR refactors some things internally to make it possible to provide
the entire gradient as the arbitrary value to each background gradient
utility, like this:
```html
<div class="bg-linear-[to_right,var(--color-red-500),var(--color-yellow-400)]">
```
This is nice if you're doing something very custom and you want to be
able to look at the whole value together, while still avoiding some of
the boilerplate you'd have if you had to write out the entire gradient
function yourself.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
These changes were extracted from our work on a `screen()` function for
CSS — we've decided to move those changes to a code mod instead of
implementing support for `screen()` in the compiler — but the
refactoring around the changes still makes sense to do so I'm landing
that here separately.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR changes how shadow color and shadow size utilities interact when
used with variants.
Take this HTML:
```html
<div class="shadow-lg shadow-red-500 hover:shadow-xl">
<!-- … -->
</div>
```
Currently this shadow would be red by default, but revert to the default
semi-transparent black color on hover.
This PR changes this behavior such that the shadow remains red on hover,
and only the shadow size changes.
We deliberately didn't do this originally because making things behave
this way makes it very difficult to get the default shadow color back
once you've changed it. The default color for `shadow-xl` for instance
is `rgb(0 0 0 / 0.1)`, and the only way to get that color back after
changing it is to know that value and explicitly bring it back:
```html
<div class="shadow-lg shadow-red-500 hover:shadow-xl hover:shadow-black/10">
<!-- … -->
</div>
```
To make things more difficult, the default shadow color is not the same
across shadow sizes. For `shadow-sm` it's `black/5`, and for
`shadow-2xl` it's `black/25`.
In practice though you basically never need to bring back the default
shadow color, so I'm reconsidering this trade-off in v4, and think I
prefer this new behavior where the color is preserved but you have to
bring back the default color if you actually need it.
A simple workaround if you don't know the color is to reset the
`--tw-shadow-color` variable like this:
```html
<div class="shadow-lg shadow-red-500 hover:shadow-xl hover:[--tw-shadow-color:initial]">
<!-- … -->
</div>
```
This relies on semi-private internals though, so perhaps we can
introduce a utility for this, like `shadow-default` or `shadow-initial`
that just unsets the shadow color.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR adds CSS codemods for migrating existing `@tailwind` directives
to the new alternatives.
This PR has the ability to migrate the following cases:
---
Typical default usage of `@tailwind` directives in v3.
Input:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
Output:
```css
@import 'tailwindcss';
```
---
Similar as above, but always using `@import` instead of `@import`
directly.
Input:
```css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
```
Output:
```css
@import 'tailwindcss';
```
---
When you are _only_ using `@tailwind base`:
Input:
```css
@tailwind base;
```
Output:
```css
@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/preflight' layer(base);
```
---
When you are _only_ using `@tailwind utilities`:
Input:
```css
@tailwind utilities;
```
Output:
```css
@import 'tailwindcss/utilities' layer(utilities);
```
---
If the default order changes (aka, `@tailwind utilities` was defined
_before_ `@tailwind base`), then an additional `@layer` will be added to
the top to re-define the default order.
Input:
```css
@tailwind utilities;
@tailwind base;
```
Output:
```css
@layer theme, components, utilities, base;
@import 'tailwindcss';
```
---
When you are _only_ using `@tailwind base; @tailwind utilities;`:
Input:
```css
@tailwind base;
@tailwind utilities;
```
Output:
```css
@import 'tailwindcss';
```
We currently don't have a concept of `@tailwind components` in v4, so if
you are not using `@tailwind components`, we can expand to the default
`@import 'tailwindcss';` instead of the individual imports.
---
`@tailwind screens` and `@tailwind variants` are not supported/necessary
in v4, so we can safely remove them.
Input:
```css
@tailwind screens;
@tailwind variants;
```
Output:
```css
```
This PR adds support for the _simple_ case of the `screens` option
inside JS config paths. This allows JS configs to extend the responsive
theme by adding custom breakpoints. Here's an example from our v3 docs:
```js
{
theme: {
screens: {
'sm': '640px',
// => @media (min-width: 640px) { ... }
'md': '768px',
// => @media (min-width: 768px) { ... }
'lg': '1024px',
// => @media (min-width: 1024px) { ... }
'xl': '1280px',
// => @media (min-width: 1280px) { ... }
'2xl': '1536px',
// => @media (min-width: 1536px) { ... }
}
}
}
```
For simple breakpoints, this will extend the core breakpoints and will
work with the `min-*` and `max-*` utilities. However, we also support
complex ways of setting up custom screens like this:
```js
{
theme: {
extend: {
screens: {
sm: { max: '639px' },
md: [
{ min: '668px', max: '767px' },
{ min: '868px' },
],
lg: { min: '868px' },
xl: { min: '1024px', max: '1279px' },
tall: { raw: '(min-height: 800px)' },
},
},
},
}
```
For these complex setups, we _only_ generate the shorthand variant (e.g.
`tall`) but those won't integrate within `min-*` and `max-*`. In v3,
adding any of these complex configurations would omit any `min-*` and
`max-*` variants.
Right now, it is possible to type `grid-cols--8` which maps to:
```css
/* Specificity: (0, 1, 0) */
.grid-cols--8 {
grid-template-columns: repeat(-8, minmax(0, 1fr));
}
```
This doesn't make sense so we used this opportunity to audit all
variants and utilities and properly disallow negative bare values.
Utilities where negative values are supported still work by using the
negative utility syntax, e.g.: `-inset-4`.
This PR adds some initial tooling for codemods. We are currently only
interested in migrating CSS files, so we will be using PostCSS under the
hood to do this. This PR also implements the "migrate `@apply`" codemod
from #14412.
The usage will look like this:
```sh
npx @tailwindcss/upgrade
```
You can pass in CSS files to transform as arguments:
```sh
npx @tailwindcss/upgrade src/**/*.css
```
But, if none are provided, it will search for CSS files in the current
directory and its subdirectories.
```
≈ tailwindcss v4.0.0-alpha.24
│ No files provided. Searching for CSS files in the current
│ directory and its subdirectories…
│ Migration complete. Verify the changes and commit them to
│ your repository.
```
The tooling also requires the Git repository to be in a clean state.
This is a common convention to ensure that everything is undo-able. If
we detect that the git repository is dirty, we will abort the migration.
```
≈ tailwindcss v4.0.0-alpha.24
│ Git directory is not clean. Please stash or commit your
│ changes before migrating.
│ You may use the `--force` flag to override this safety
│ check.
```
---
This PR alsoo adds CSS codemods for migrating existing `@apply`
directives to the new version.
This PR has the ability to migrate the following cases:
---
In v4, the convention is to put the important modifier `!` at the end of
the utility class instead of right before it. This makes it easier to
reason about, especially when you are variants.
Input:
```css
.foo {
@apply !flex flex-col! hover:!items-start items-center;
}
```
Output:
```css
.foo {
@apply flex! flex-col! hover:items-start! items-center;
}
```
---
In v4 we don't support `!important` as a marker at the end of `@apply`
directives. Instead, you can append the `!` to each utility class to
make it `!important`.
Input:
```css
.foo {
@apply flex flex-col !important;
}
```
Output:
```css
.foo {
@apply flex! flex-col!;
}
```
This PR improves how the `text-{size}` utilities interact with the
`leading-*`, `tracking-*`, and `font-{weight}` utilities, ensuring that
if the user explicitly uses any of those utilities that those values are
not squashed by any defaults baked into the `text-{size}` utilities.
Prior to this PR, if you wrote something like this:
```html
<div class="text-lg leading-none md:text-2xl">
```
…the `leading-none` class would be overridden by the default line-height
value baked into the `text-2xl` utility at the `md` breakpoint. This has
been a point of confusion and frustration for people [in the
past](https://github.com/tailwindlabs/tailwindcss/issues/6504) who are
annoyed they have to keep repeating their custom `leading-*` value like
this:
```html
<div class="text-lg leading-none md:text-2xl md:leading-none lg:text-4xl lg:leading-none">
```
This PR lets you write this HTML instead but get the same behavior as
above:
```html
<div class="text-lg leading-none md:text-2xl lg:text-4xl">
```
It's important to note that this change _only_ applies to line-height
values set explicitly with a `leading-*` utility, and does not apply to
the line-height modifier.
In this example, the line-height set by `text-sm/6` does _not_ override
the default line-height included in the `md:text-lg` utility:
```html
<div class="text-sm/6 md:text-lg">
```
That means these two code snippets behave differently:
```html
<div class="text-sm/6 md:text-lg">…</div>
<div class="text-sm leading-6 md:text-lg">…</div>
```
In the top one, the line-height `md:text-lg` overrides the line-height
set by `text-sm/6`, but in the bottom one, the explicit `leading-6`
utility takes precedence.
This PR applies the same improvements to `tracking-*` and
`font-{weight}` as well, since all font size utilities can also
optionally specify default `letter-spacing` and `font-weight` values.
We achieve this using new semi-private CSS variables like we do for
things like transforms, shadows, etc., which are set by the `leading-*`,
`tracking-*`, and `font-{weight}` utilities respectively. The
`text-{size}` utilities always use these values first if they are
defined, and the default values become fallbacks for those variables if
they aren't present.
We use `@property` to make sure these variables are reset to `initial`
on a per element basis so that they are never inherited, like with every
other variable we define.
This PR does slightly increase the amount of CSS generated, because now
utilities like `leading-5` look like this:
```diff
.leading-5 {
+ --tw-leading: 1.25rem;
line-height: 1.25rem;
}
```
…and utilites like `text-sm` include a `var(…)` lookup that they didn't
include before:
```diff
.text-sm {
font-size: 0.875rem;
- line-height: var(--font-size-sm--line-height, 1.25rem);
+ line-height: var(--tw-leading, var(--font-size-sm--line-height, 1.25rem));
}
```
If this extra CSS doesn't feel worth it for the small improvement in
behavior, we may consider just closing this PR and keeping things as
they are.
This PR is also a breaking change for anyone who was depending on the
old behavior, and expected the line-height baked into the `md:text-lg`
class to take precedence over the explicit `leading-6` class:
```html
<div class="text-sm leading-6 md:text-lg">…</div>
```
Personally I am comfortable with this because of the fact that you can
still get the old behavior by preferring a line-height modifier:
```html
<div class="text-sm/6 md:text-lg">…</div>
```
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Resolves#14440.
This PR fixes an issue where registering a custom `inset-shadow-*`
utility value in your theme like this:
```css
@theme {
--inset-shadow-potato: inset 0px 2px 6px #7a4724;
}
```
…mistakenly generates both an `inset-shadow-*` and `inset-*` utility
with that value:
```css
.inset-shadow-potato {
inset: inset 0px 2px 6px #7a4724;
}
.inset-shadow-potato {
box-shadow: inset 0px 2px 6px #7a4724;
}
```
This replaces #14445 which turns out to not be the ideal solution.
Now we just explicitly ignore variables like `--inset-shadow-*` and
`--inset-ring-*` in the `inset` handler. Kind of a gross patch but I can
live with it because the whole existence of the `--inset-*` key is kind
of a backwards compatibility thing anyways.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Right now when we encounter a candidates with invalid `theme()` calls we
throw an error which stops the build entirely. This is not ideal
because, especially in the case of `node_modules`, if one file in one
package has an error it will stop the build for an entire project and
tracking this down can be quite difficult.
Now, after this PR, any candidates that use `theme(…)` with non-existent
theme keys (e.g. `rounded-[theme(--radius-does-not-exist)]`) will be
skipped instead of breaking the build.
Before:
```html
<div class="underline rounded-[theme(--radius-does-not-exist)]"></div>
```
```css
/* No CSS was generated because an error was thrown */
/* Error: Invalid theme key: --radius-does-not-exist */
```
After:
```html
<div class="underline rounded-[theme(--radius-does-not-exist)]"></div>
```
```css
.underline {
text-decoration-line: underline;
}
```
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR fixes an issue where using `anchor-size` in arbitrary values
resulted in the incorrect css.
Input: `w-[calc(anchor-size(width)+8px)]`
Output:
```css
.w-\[calc\(anchor-size\(width\)\+8px\)\] {
width: calc(anchor - size(width) + 8px);
}
```
This PR fixes that, by generating the correct CSS:
```css
.w-\[calc\(anchor-size\(width\)\+8px\)\] {
width: calc(anchor-size(width) + 8px);
}
```
This PR adds support for the `aria`, `supports`, and `data` properties
found in JS config options. In v3, you could extend the theme to add
more variants by using an object syntax like this:
```ts
{
theme: {
extend: {
aria: {
polite: 'live="polite"',
},
supports: {
'child-combinator': 'h2 > p',
},
data: {
checked: 'ui~="checked"',
},
},
}
}
```
Since we no longer rely on theme variables for these variants, the way
to make this work is by adding custom variants for each of these
manually added variants.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR fixes an issue with the order of CSS when using stacked variants
when two variants have the same order (as defined by the custom
comperator function).
## The problem
Take, for example, our breakpoint variants. Those are split into `max-*`
variants and a group containing all `min-*` variants as well as the
unprefixed static ones (e.g. `lg`, `sm`).
We currently define a custom sort order for all breakpoints variants
that will compare their order based on the resolved value provided. So
if you define `--breakpoint-sm: 100px` and `--breakpoint-lg: 200px`, we
first check if both breakpoints have the same unit and then we rank
based on the numerical value, making `sm` appear before `lg`.
But since the `min-*` variant and the `sm` variant share the same group,
this also means that `min-sm` and `sm` as well as `min-lg` and `lg` will
always have the same order (which makes sense—they also have the exact
same CSS they generate!)
The issue now arises when you use these together with variant stacking.
So, say you want to stack the two variants `max-lg:min-sm`. We always
want stacked variants to appear _after_ their non-stacked individual
parts (since they are more specific). To do this right now, we generate
a bitfield based on the variant order. If you have four variants like
this:
| Order | Variant |
| ------------- | ------------- |
| 0 | `max-lg` |
| 1 | `max-sm` |
| 2 | `min-sm` |
| 3 | `min-lg` |
We will assign one bit for each used variant starting from the lowest
bit, so for the stack `max-lg:min-sm` we will set the bitfield to `0101`
and those for the individual variants would result in `0100` (for
`min-sm`) and `0001` (for `max-lg`). We then convert this bitfield to a
number and order based on that number. This ensures that the stack
always sorts higher.
The issue now arises from the fact that the variant order also include
the unprefixed variants for a breakpoint. So in our case of `lg` and
`sm`, the full list would look like this:
| Order | Variant |
| ------------- | ------------- |
| 0 | `max-lg` |
| 1 | `max-sm` |
| 2 | `min-sm` |
| 3 | `sm` |
| 4 | `min-lg` |
| 5 | `lg` |
This logic now breaks when you start to compute a stack for something
like `max-lg:min-lg` _while also using the `lg` utility:
| Stack | Bitmap | Integer Value |
| ------------- | ------------- | ------------- |
| `max-lg:min-lg` | `010001` | 17 |
| `lg` | `100000` | 18 |
As you can see here, the sole `lg` variant will now sort higher than the
compound of `max-lg:min-lg`. That's not something we want!
## Proposed solution
To fix this, we need to encode the information of _same_ variant order
somehow. A single array like the example above is not sufficient for
this, since it will remove the information of the similar sort order.
Instead, we now computed a list of nested arrays for the order lookup
that will combine variants of similar values (while keeping the order
the same). So from the 6 item array above, we now have the following
nested array:
| Order | Variant |
| ------------- | ------------- |
| 0 | [`max-lg`] |
| 1 | [`max-sm`] |
| 2 | [`min-sm`, `sm`] |
| 3 | [`min-lg`, `lg`] |
When we use the first layer index for the bitfield, we can now see how
this solves the issue:
| Stack | Bitmap | Integer Value |
| ------------- | ------------- | ------------- |
| `max-lg:min-lg` | `1001` | 9 |
| `lg` | `1000` | 8 |
That's pretty-much it! There are a few other changes in this PR that
mostly handles with a small regression by this change where now, named
`group` variants and unnamed `group` variants would now have the same
order (something that was undefined behavior before).
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Fixes an issue where `borderRadius` was not properly upgraded when using
it in the `theme()` function like this:
```
rounded-[theme(borderRadius.lg)]
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR builds on top of #14365 and adds a few more changes we discussed
during a sync on the latter PR:
- We now split `plugin-api.ts` into two files and moved it into
`compat/`. One file is now defining the comat plugin API only where as
another file deals with the compat hook.
- The walk for `@plugin` and `@config` is now happening inside the
compat hook.
The one remaining work item is to change the `loadPlugin` and
`loadConfig` APIs to a more unified `resolveModule` one that does not
care on what we try to load it for. I suggest we should make this change
at the same time we start working on finalizing the `tailwindcss` APIs,
since a lot of things will have to be rethought then anyways.
This PR works around a current regression in the Rust toolchain that
caused our Windows workers to start failing with:
```
Finished `test` profile [unoptimized + debuginfo] target(s) in 32.63s
Running unittests src\lib.rs (target\debug\deps\tailwind_oxide-ce6a5d43a3798437.exe)
Load Node-API [napi_get_last_error_info] from host runtime failed: GetProcAddress failed
fatal runtime error: thread::set_current should only be called once per thread
Load Node-API [napi_get_uv_event_loop] from host runtime failed: GetProcAddress failed
Load Node-API [napi_fatal_exception] from host runtime failed: GetProcAddress failed
Load Node-API [napi_create_threadsafe_function] from host runtime failed: GetProcAddress failed
error: test failed, to rerun pass `-p tailwind-oxide --lib`
```
The workaround is to pin the rust toolchain version so that the
regression isn't applied when we build on Windows in test mode.
While upgrading a project to Tailwind CSS v4, I forgot to remove the
`tailwindcss` import from the PostCSS config. As a result of this, I was
greeted with the following message:
```
node:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */);
^
[Failed to load PostCSS config: Failed to load PostCSS config (searchPath: /Users/philipp/dev/project): [TypeError] Invalid PostCSS Plugin found at: plugins[0]
(@/Users/philipp/dev/project/postcss.config.js)
TypeError: Invalid PostCSS Plugin found at: plugins[0]
```
I don't think this was particularly helpful, so I’m proposing we add a
default function export to the `tailwindcss` package so when it's used
inside PostCSS, we can control the error message. So I changed it to
something along these lines:
```
It looks like you're trying to use the \`tailwindcss\` package as a PostCSS plugin. This is no longer possible since Tailwind CSS v4.
If you want to continue to use Tailwind CSS with PostCSS, please install \`@tailwindcss/postcss\` and change your PostCSS config file.
at w (/Users/philipp/dev/project/node_modules/tailwindcss/node_modules/tailwindcss/dist/lib.js:1:21233)
at Object.<anonymous> (/Users/philipp/dev/project/node_modules/tailwindcss/postcss.config.cjs:3:13)
at Module._compile (node:internal/modules/cjs/loader:1358:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
at Module.load (node:internal/modules/cjs/loader:1208:32)
at Module._load (node:internal/modules/cjs/loader:1024:12)
at cjsLoader (node:internal/modules/esm/translators:348:17)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:297:7)
at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:316:24)
```
This is also a good place to link to the migration guides once we have
them 🙂
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR adds support for the `matchVariant` plugin API. I've copied over
all [V3
tests](f07dbff2a7/tests/match-variants.test.js)
and made sure they still pass.
## Sorted order of stacked arbitrary variants
The only difference in behavior is regarding the sort order of stacked
arbitrary variants: Sorting in this case now works by the latest defined
`matchVariant` taking precedence.
So, if you define a plugin like this:
```ts
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
sort(a, z) {
return parseInt(a.value) - parseInt(z.value)
},
})
matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
sort(a, z) {
return parseInt(z.value) - parseInt(a.value)
},
})
```
The resulting CSS is first sorted by the `testmax` values descending and
then the `testmin` values ascending, so these candidates:
```txt
testmin-[150px]:testmax-[400px]:order-2
testmin-[100px]:testmax-[350px]:order-3
testmin-[100px]:testmax-[300px]:order-4
testmin-[100px]:testmax-[400px]:order-1
```
Will resolve to the order outlined by the `order-` utility.
## At-rules and placeholders support
Since we added support for at-rules and placeholders in the
`matchVariant` syntax like this:
```ts
matchVariant(
'potato',
(flavor) => `@media (potato: ${flavor}) { @supports (font:bold) { &:large-potato } }`,
)
```
We also added support for the same syntax to the `addVariant` API:
```ts
addVariant(
'potato',
'@media (max-width: 400px) { @supports (font:bold) { &:large-potato } }',
)
```
The only change necessary in core was to call functional variants for
when the variant value is set to `null`. This allows functional variants
to define the un-parameterized implementation like `potato:underline` as
opposed to `potato[big]:underline`.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
I noticed a lot more backwards compatibility concerns had started
leaking into core, especially around the `theme` function, so did a bit
of work to try and pull that stuff out and into the compatibility layer.
Now the core version of `theme` only handles CSS variables (like
`--color-red-500`) and has no knowledge of the dot notation or how to
upgrade it. Instead, we unconditionally override that function in the
compatibility layer with a light version that _does_ know how to do the
dot notation upgrade, and override that again with the very heavy/slow
version that handles JS config objects only if plugins/JS configs are
actually used.
I've also renamed `registerPlugins` to `applyCompatibilityHooks` because
the name was definitely a bit out of date given how much work it's doing
now, and now call it unconditionally from core, leaving that function to
do any conditional optimizations itself internally.
Next steps I think would be to split up `plugin-api.ts` a bit and maybe
make `applyCompatibilityHooks` its own file, and move both of those
files into the `compat` folder so everything is truly isolated there.
My goal with this stuff is that if/when we ever decide to drop backwards
compatibility with these features in the future (maybe v5), that all we
have to do is delete the one line of code that calls
`applyCompatibilityHooks` in `index.ts`, and delete the `compat` folder
and we're done. I could be convinced that this isn't a worthwhile goal
if we feel it's making the codebase needlessly complex, so open to that
discussion as well.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
The internal `registerPlugins()` API is used to enable backwards
compatibility with v3 plugins and configs and it is called on every
build even when no v3 plugins or configs are used. This function has a
non-trivial cost in that case — around 5ms.
So this PR does a few things:
## Implements a simpler, faster `theme(…)` function
We now have a much simpler `theme(…)` function that can be used when
backwards compatibility is not necessary. It still supports many of the
same features:
- The modern, v4 style CSS variable syntax `theme(--color-red-500)`
- The legacy, v3 path style `theme(colors.red.500)`
- And the v3-style alpha modifier `theme(colors.red.500 / 50%)`
- Path upgrades so things like `theme(accentColor.red.500)` pulls from
`--color-red-500` when no `--accent-color-red-500` theme key exists
When you do have plugins or configs the more advanced `theme(…)`
function is swapped in for more complete backwards compatibility.
## `registerPlugins` registers globs
Before `registerPlugins` passed the `ResolvedConfig` out so we could
register globs in `compile()`. Since that one function is really the
main driver for backwards compat we decided to move the content path
registration into `registerPlugins` itself when it comes to paths
provided by plugins and configs.
This is an internal implementation detail (well this entire PR is) but
it's worth mentioning. This method is used to resolve a theme value from
a theme key.
## `registerPlugins` is now only called when necessary
All of the above work made it so that `registerPlugins` can be called
only as needed. This means that when no v3 plugins or configs are used,
`registerPlugins` is never called thus elminating the performance impact
of config resolution.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Right now the following does not work and instead produces a type error:
```
import { type Config } from 'tailwindcss'
export default {
// … config here
} satisfies Config
```
We were not exporting a `Config` type but thankfully this already exists
in the codebase so we just need to export it.
It does _not_ have all properties of an existing config as not all
features have been implemented (or in some cases necessary / relevant
for v4).
Notably missing are:
- `important`
- `prefix`
- `separator`
- `safelist`
- `blocklist`
- `future`
- `experimental`
- `corePlugins`
Also, explicit keys for theme are not currently specified but we should
probably bring this back even if just as an auto-complete aid.
Previously, given the following CSS and configuration:
```css
/* app.css */
@theme default {
--font-size-base: 1.25rem;
--font-size-base--line-height: 1.5rem;
}
@tailwind utilities;
@config "./config.js";
```
```js
// config.js
export default {
theme: {
fontSize: {
// …
base: ['1rem', { lineHeight: '1.75rem' }],
},
// …
},
};
```
When a config or a plugin asked for the value of `theme(fontSize.base)`
like so:
```js
// config.js
export default {
theme: {
// …
typography: ({ theme }) => ({
css: {
'[class~="lead"]': {
fontSize: theme('fontSize.base')[0],
...theme('fontSize.base')[1],
},
}
}),
},
};
```
We would instead pull the values from the CSS theme even through they're
marked with `@theme default`. This would cause the incorrect font size
and line height to be used resulting in something like this (in the case
of the typography plugin with custom styles):
```css
.prose [class~="lead"] {
font-size: 1.25rem;
line-height: 1.5rem;
}
```
After this change we'll now pull the values from the appropriate place
(the config in this case) and the correct font size and line height will
be used:
```css
.prose [class~="lead"] {
font-size: 1rem;
line-height: 1.75rem;
}
```
This will work even when some values are overridden in the CSS theme:
```css
/* app.css */
@theme default {
--font-size-base: 1.25rem;
--font-size-base--line-height: 1.5rem;
}
@theme {
--font-size-base: 2rem;
}
@tailwind utilities;
@config "./config.js";
```
which would result in the following CSS:
```css
.prose [class~="lead"] {
font-size: 2rem;
line-height: 1.75rem;
}
```
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR fixes an issue where theme function calls like
`theme('transitionTimingFunction.DEFAULT')` would incorrectly resolve to
an object when the set of defined CSS theme values looked like this:
```css
@theme {
--transition-timing-function-in: ease-in;
--transition-timing-function-out: ease-out;
--transition-timing-function-in-out: ease-out;
}
```
We were mistakenly retrieving the entire
`--transition-timing-function-*` namespace in this case and returning an
object, even though the user is explicitly asking for a single value by
including `.DEFAULT` in their call.
This ensures it resolves to null instead. Fixes an issue I ran into on
this live stream earlier today:
https://x.com/adamwathan/status/1831740214051799281
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
There are still instances in which CI is flaky after #14332. This PR
applies the same fix (that is, moving the file write into the retrying
block) to all `retryAssertion` callbacks.
When we observe that no new candidates were found, then we can return
early because nothing really changed. There is also no need to
re-optimize (use Lightning CSS) in this case.
But this had a side effect that when no new candidates were detected,
that you didn't see any output either. This feels like nothing is
working from a DX perspective.
Typically you are changing things, so it's not really a problem. But the
moment you use a class that already existed (e.g.: in another file) you
also don't get any output because we have a shared cache.
This PR solves that by always showing the output. But it still doesn't
write to disk if nothing changed.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR moves support for opacity modifies from the CSS `theme()`
function into the plugin `theme()` implementation, this will allow
plugins to use this, too:
```ts
let plugin = plugin(function ({ addUtilities, theme }) {
addUtilities({
'.percentage': {
color: theme('colors.red.500 / 50%'),
},
'.fraction': {
color: theme('colors.red.500 / 0.5'),
},
'.variable': {
color: theme('colors.red.500 / var(--opacity)'),
},
})
})
}
```
There's a small behavioral change for the CSS `theme()` function. Since
tuples are resolved by default for the CSS `theme()` function only,
these will no longer have opacity applied to their first values. This is
probably fine given the reduced complexity as I don't expect the first
values of tuples to be colors and the fix would mean we would have to
parse the modifier in different places.