This PR adds support for a new `inline` option when defining a `@theme`
block that tells Tailwind to use raw theme values for utilities instead
of referencing the corresponding generated CSS variable.
```css
/* Input */
@theme inline {
--color-red-500: #ef4444;
/* ... */
}
/* Example output */
:root {
--color-red-500: #ef4444;
}
.text-red-500 {
color: #ef4444;
}
```
This can be composed with the existing `reference` option in case you
want to define a `@theme` block as both `reference` (so the variables
aren't generated) and `inline`:
```css
/* Input */
@theme inline reference {
--color-red-500: #ef4444;
/* ... */
}
/* Example output */
.text-red-500 {
color: #ef4444;
}
```
Since you can have multiple `@theme` blocks, you can even define some
values normally and some as inline based on how you're using them. For
example you might want to use `inline` for defining literal tokens like
`--color-red-500`, but include the variable for tokens that you want to
be able to theme like `--color-primary`:
```css
/* Input */
@theme inline {
--color-red-500: #ef4444;
/* ... */
}
@theme {
--color-primary: var(--color-red-500);
}
/* Example output */
:root {
--color-red-500: #ef4444;
--color-primary: var(--color-red-500);
}
.text-red-500 {
color: #ef4444;
}
.text-primary {
color: var(--color-primary, var(--color-red-500));
}
```
## Breaking changes
Prior to this PR, you could `@import` a stylesheet that contained
`@theme` blocks as reference by adding the `reference` keyword to your
import:
```css
@import "./my-theme.css" reference;
```
Now that `reference` isn't the only possible option when declaring your
`@theme`, this syntax has changed to a new `theme(…)` function that
accepts `reference` and `inline` as potential space-separated values:
```css
@import "./my-theme.css";
@import "./my-theme.css" theme(reference);
@import "./my-theme.css" theme(inline);
@import "./my-theme.css" theme(reference inline);
```
If you are using the `@import … reference` option with an earlier alpha
release, you'll need to update your code to `@import … theme(reference)`
once this PR lands in a release.
## Motivation
This PR is designed to solve an issue pointed out in #14091.
Prior to this PR, generated utilities would always reference variables
directly, with the raw value as a fallback:
```css
/* Input */
@theme {
--color-red-500: #ef4444;
/* ... */
}
/* Example output */
:root {
--color-red-500: #ef4444;
}
.text-red-500 {
color: var(--color-red-500, #ef4444);
}
```
But this can create issues with variables resolving to an unexpected
value when a theme value is referencing another variable defined on
`:root`.
For example, say you have a CSS file like this:
```css
:root, .light {
--text-fg: #000;
}
.dark {
--text-fg: #fff;
}
@theme {
--color-fg: var(--text-fg);
}
```
Without `@theme inline`, we'd generate this output if you used the
`text-fg` utility:
```css
:root, .light {
--text-fg: #000;
}
.dark {
--text-fg: #fff;
}
:root {
--color-fg: var(--text-fg);
}
.text-fg {
color: var(--color-fg, var(--text-fg));
}
```
Now if you wrote this HTML, you're probably expecting your text to be
the dark mode color:
```html
<div class="dark">
<h1 class="text-fg">Hello world</h1>
</div>
```
But you'd actually get the light mode color because of this rule:
```css
:root {
--color-fg: var(--text-fg);
}
.text-fg {
color: var(--color-fg, var(--text-fg));
}
```
The browser will try to resolve the `--color-fg` variable, which is
defined on `:root`. When it tries to resolve the value, _it uses the
value of `var(--text-fg)` as it would resolve at `:root`_, not what it
would resolve to based on the element that has the `text-fg` class.
So `var(--color-fg)` resolves to `#000` because `var(--text-fg)`
resolved to `#000` at the point in the tree where the browser resolved
the value of `var(--color-fg)`.
By using `@theme inline`, the `.text-fg` class looks like this:
```css
.text-fg {
color: var(--text-fg);
}
```
With this definition, the browser doesn't try to resolve `--color-fg` at
all and instead resolves `--text-fg` directly which correctly resolves
to `#fff` as expected.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR adds a new root `/integrations` folder that will be the home of
integration tests. The idea of these tests is to use Tailwind in various
setups just like our users would (by only using the publishable npm
builds).
To avoid issues with concurrent tests making changes to the file system,
to make it very easy to test through a range of versions, and to avoid
changing configuration objects over and over in test runs, we decided to
inline the scaffolding completely into the test file and have no
examples checked into the repo.
Here's an example of how this can look like for a simple Vite test:
```ts
test('works with production builds', {
fs: {
'package.json': json`
{
"type": "module",
"dependencies": {
"@tailwindcss/vite": "workspace:^",
"tailwindcss": "workspace:^"
},
"devDependencies": {
"vite": "^5.3.5"
}
}
`,
'vite.config.ts': ts`
import tailwindcss from '@tailwindcss/vite'
import { defineConfig } from 'vite'
export default defineConfig({
build: { cssMinify: false },
plugins: [tailwindcss()],
})
`,
'index.html': html`
<head>
<link rel="stylesheet" href="./src/index.css">
</head>
<body>
<div class="underline m-2">Hello, world!</div>
</body>
`,
'src/index.css': css`
@import 'tailwindcss/theme' reference;
@import 'tailwindcss/utilities';
`,
},
},
async ({ fs, exec }) => {
await exec('pnpm vite build')
expect.assertions(2)
for (let [path, content] of await fs.glob('dist/**/*.css')) {
expect(path).toMatch(/\.css$/)
expect(stripTailwindComment(content)).toMatchInlineSnapshot(
`
".m-2 {
margin: var(--spacing-2, .5rem);
}
.underline {
text-decoration-line: underline;
}"
`,
)
}
},
)
```
By defining all dependencies this way, we never have to worry about
which fixtures are checked in and can more easily describe changes to
the setup.
For ergonomics, we've also added the [`embed` prettier
plugin](https://github.com/Sec-ant/prettier-plugin-embed). This will
mean that files inlined in the `fs` setup are properly indented. No
extra work needed!
If you're using VS Code, I can also recommend the [Language
Literals](https://marketplace.visualstudio.com/items?itemName=sissel.language-literals)
extension so that syntax highlighting also _just works_.
A neat feature of inlining the scaffolding like this is to make it very
simple to test through a variety of versions. For example, here's how we
can set up a test against Vite 5 and Vite 4:
```js
;['^4.5.3', '^5.3.5'].forEach(viteVersion => {
test(`works with production builds for Vite ${viteVersion}`, {
fs: {
'package.json': json`
{
"type": "module",
"devDependencies": {
"vite": "${viteVersion}"
}
}
`,
async () => {
// Do something
},
)
})
```
## Philosophy
Before we dive into the specifics, I want to clearly state the design
considerations we have chosen for this new test suite:
- All file mutations should be done in temp folders, nothing should ever
mess with your working directory
- Windows as a first-class citizen
- Have a clean and simple API that describes the test setup only using
public APIs
- Focus on reliability (make sure cleanup scripts work and are tolerant
to various error scenarios)
- If a user reports an issue with a specific configuration, we want to
be able to reproduce them with integration tests, no matter how obscure
the setup (this means the test need to be in control of most of the
variables)
- Tests should be reasonably fast (obviously this depends on the
integration. If we use a slow build tool, we can't magically speed it
up, but our overhead should be minimal).
## How it works
The current implementation provides a custom `test` helper function
that, when used, sets up the environment according to the configuration.
It'll create a new temporary directory and create all files, ensuring
things like proper `\r\n` line endings on Windows.
We do have to patch the `package.json` specifically, since we can not
use public versions of the tailwindcss packages as we want to be able to
test against a development build. To make this happen, every `pnpm
build` run now creates tarballs of the npm modules (that contain only
the files that would also in the published build). We then patch the
`package.json` to rewrite `workspace:^` versions to link to those
tarballs. We found this to work reliably on Windows and macOS as well as
being fast enough to not cause any issues. Furthermore we also decided
to use `pnpm` as the version manager for integration tests because of
it's global module cache (so installing `vite` is fast as soon as you
installed it once).
The test function will receive a few utilities that it can use to more
easily interact with the temp dir. One example is a `fs.glob` function
that you can use to easily find files in eventual `dist/` directories or
helpers around `spawn` and `exec` that make sure that processes are
cleaned up correctly.
Because we use tarballs from our build dependencies, working on changes
requires a workflow where you run `pnpm build` before running `pnpm
test:integrations`. However it also means we can run clients like our
CLI client with no additional overhead—just install the dependency like
any user would and set up your test cases this way.
## Test plan
This PR also includes two Vite specific integration tests: One testing a
static build (`pnpm vite build`) and one a dev mode build (`pnpm vite
dev`) that also makes changes to the file system and asserts that the
resources properly update.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR updates vitest to v2. The changes are mostly around using fork
instead of threads for how tests are run which should fix one of the
issues we've found.
Ever since adding the unit tests on Windows, we started seeing
occacional flags of vitest crashing with the following error:
```
ELIFECYCLE Command failed with exit code 3221225477.
Error: Process completed with exit code 1.
```
When reading the [v2
changelog](https://github.com/vitest-dev/vitest/releases/tag/v2.0.0) we
saw many bug fixes related to segfaulting so we believe this was the
issue.
When upgrading `vitest` alone, we got a bunch of dependency mismatches
though (specifically, vite was installed two times with different peer
dependencies for `@types/node` which causes our vite plugin's `Plugin`
type to be different from the one in the vite playground. Yikes. These
were eventually fixed by having pnpm create a new lockfile for us. So,
unfortunatly this PR also bumps a bunch of patch versions for some
transitive dependencies. Tests seem fine, though 🤞
This PR also removes the `bench` script from CI. It doesn't give us
value in its current state (since it's not reporting when performance
regresses) but added a few seconds of unnecessary overhead to each test
run.
This changes the V4 CI to run on any pull request change (so an opened,
reopened, and updated PR), regardless if the PR is directed into the
`next` branch or not.
This is helpful for testing stacked PRs like:
https://github.com/tailwindlabs/tailwindcss/pull/14078
Last week we discussed bringing in some consistency for our non-public
npm packages in the repo. We discussed using custom namespaces (e.g.
`@tailwindcss-internal`) vs. simple prefixes but it does not matter too
much if we are both consistent with our pattern and it's easy for us to
see whether a plugin is public or not.
Since we have a mixture of public namespaced (`@tailwindcss/*`) and
non-namespaced (`tailwindcss`) packages, I think it would be best if we
use a prefix to signal internal dependencies. This PR proposes we use
`internal-*` as the prefix and renames `test-utils` to
`internal-example-plugin` (since, really, this package is just an
example for the Tailwind plugin system).
This PR changes the GitHub action workflow for V4 to start running all
unit tests and build on both Ubuntu (our current default) _and_ Windows.
This is to ensure we catch issues with paths and other Windows-specific
things sooner. It does, however, not replace testing on Windows.
This PR reduces the specificity of the * variant so that classes
directly on the child elements take precedence over the styles applied
by the parent.
Previously a utility like `*:flex` would generate this CSS:
```css
.\*\:flex > * {
display: flex;
}
```
This selector has a specificity of `0,1,0`, which is the same as the
specificity for a bare utility class like `flex`, `block`, or `grid`.
Because variants always appear later in the CSS file than bare
utilities, this means that given this HTML, the `grid` class on the
child element would have no effect:
```html
<div class="*:flex">
<div>...</div>
<div class="grid">...</div>
<div>...</div>
</div>
```
After this PR, the `*:flex` utility generates this CSS instead:
```css
:where(.\*\:flex > *) {
display: flex;
}
```
This selector has a specificity of `0,0,0`, so even though it appears
later in the CSS, a bare utility with a specificity of `0,1,0` will
still take precedence.
This is something we wanted to do when we first introduced the `*`
variant in the v3 series, but couldn't because having such a low
specificity meant that styles in Preflight would take precedence over
utilities like `*:flex`, which is not would anyone would want.
We can make this change for v4 because now all of Preflight is wrapped
in a dedicated `@layer`, and rules from later layers always take
precedence over rules from earlier layers even if the rule in the later
layer has a lower specificity.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This PR allows you to add custom utilities to your project via the new
`@utility` rule.
For example, given the following:
```css
@utility text-trim {
text-box-trim: both;
text-box-edge: cap alphabetic;
}
```
A new `text-trim` utility is available and can be used directly or with
variants:
```html
<div class="text-trim">...</div>
<div class="hover:text-trim">...</div>
<div class="lg:dark:text-trim">...</div>
```
If a utility is defined more than one time the latest definition will be
used:
```css
@utility text-trim {
text-box-trim: both;
text-box-edge: cap alphabetic;
}
@utility text-trim {
text-box-trim: both;
text-box-edge: cap ideographic;
}
```
Then using `text-trim` will produce the following CSS:
```css
.text-trim {
text-box-trim: both;
text-box-edge: cap ideographic;
}
```
You may also override specific existing utilities with this — for
example to add a `text-rendering` property to the `text-sm` utility:
```css
@utility text-sm {
font-size: var(--font-size-sm, 0.875rem);
line-height: var(--font-size-sm--line-height, 1.25rem);
text-rendering: optimizeLegibility;
}
```
Though it's preferred, should you not need to add properties, to
override the theme instead.
Lastly, utilities with special characters do not need to be escaped like
you would for a class name in a selector:
```css
@utility push-1/2 {
right: 50%;
}
```
We do however explicitly error on certain patterns that we want to
reserve for future use, for example `push-*` and `push-[15px]`.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Fixes#14026
See https://github.com/tailwindlabs/tailwindcss/pull/14037 for the v3
fix
When translating `data-` and `aria-` modifiers with attribute selectors,
we currently do not wrap the target attribute in quotes. This only works
for keywords (purely alphabetic words) but breaks as soon as there are
numbers or things like spaces in the attribute:
```html
<div data-id="foo" class="data-[id=foo]:underline">underlined</div>
<div data-id="f1" class="data-[id=1]:underline">not underlined</div>
<div data-id="foo bar" class="data-[id=foo_bar]:underline">not underlined</div>
```
Since it's fairly common to have attribute selectors with `data-` and
`aria-` modifiers, this PR will now wrap the attribute in quotes unless
these are already wrapped.
| Tailwind Modifier | CSS Selector |
| ------------- | ------------- |
| `.data-[id=foo]` | `[data-id='foo']` |
| `.data-[id='foo']` | `[data-id='foo']` |
| `.data-[id=foo_i]` | `[data-id='foo i']` |
| `.data-[id='foo'_i]` | `[data-id='foo' i]` |
| `.data-[id=123]` | `[data-id='123']` |
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
* implement `@variant` in CSS
* implement `addVariant(name, objectTree)`
* update changelog
* ensure that `@variant` can only be used top-level
* simplify Plugin API type
* Use type instead of interface (for now)
* Use more realistic variant for test
* Allow custom properties to use `@slot` as content
* Use "cannot" instead of "can not"
* Remove `@variant` right away
* Throw when `@variant` is missing a selector or body
* Use "CSS-in-JS" terminology instead of "CSS Tree"
* Rename tests
* Mark some tests that seem wrong
* Tweak comment, remove unnecessary return
* Ensure group is usable with custom selector lists
* Only apply extra `:is(…)` when there are multiple selectors
* Tweak comment
* Throw when @variant has both selector and body
* Rework tests to use more realistic examples
* Compound variants on an isolated copy
This prevents traversals from leaking across variants
* Handle selector lists for peer variants
* Ignore at rules when compounding group and peer variants
* Re-enable skipped tests
* Update changelog
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
* Add basic `addVariant` plugin support
* Return early
* Load plugins right away instead later
* Use correct type for variant name
* Preliminary support for addVariant plugins in PostCSS plugin
* Add test for compounding plugin variants
* Add basic `loadPlugin` support to Vite plugin
* Add basic `loadPlugin` support to CLI
* add `io.ts` for integrations
* use shared `loadPlugin` from `tailwindcss/io`
* add `tailwindcss-test-utils` to `@tailwindcss/cli` and `@tailwindcss/vite`
* only add `tailwindcss-test-utils` to `tailwindcss` as a dev dependency
Because `src/io.ts` is requiring the plugin.
* move `tailwindcss-test-utils` to `@tailwindcss/postcss `
This is the spot where we actually need it.
* use newer pnpm version
* Duplicate loadPlugin implementation instead of exporting io file
* Remove another io reference
* update changelog
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
* ensure that static utilities do not take a `modifier`
* do not allow multiple segments for now
Right now, `bg-red-1/2/3` should not parse
* add tests for variants that don't accept a modifier
* ensure static variants do not accept a modifier
* do not accept a modifier for some variants
* add tests for utilities that don't accept a modifier
* do not accept a modifier for some utilities
* update changelog
* re-add sorting related test
* ensure opacity modifier variables work as expected
We use `color-mix()` in v4 which means that we can use this for the
opacity modifier. One thing we do already is convert values such as
`0.5` to `50%` because that's what the `color-mix()` function expects.
However, if you use a variable like this:
```html
<div class="[--opacity:0.5] bg-red-500/[var(--opacity)]"></div>
```
This currently generates:
```css
.bg-red-500\/\[var\(--opacity\)\] {
background-color: color-mix(
in srgb,
var(--color-red-500, #ef4444) var(--opacity),
transparent
);
}
.\[--opacity\:0\.5\] {
--opacity: 0.5;
}
```
Which won't work because the opacity variable resolves to `0.5` instead
of the expected`50%`.
This commit fixes that by always ensuring that we use `* 100%`.
- If you already had a percentage, we would have `calc(50% * 100%)`
which is `50%`.
- If we have `0.5` then we would have `calc(0.5 * 100%)` which is also
`50%`.
* wrap everything but percentages in `calc(… * 100%)`
* use `else if`
* update changelog
* add test that verifies that parsing `bg-red-[#0088cc]` should not work
* improve parsing of arbitrary values, root is known
When using arbitrary values such as `bg-[#0088cc]` then we know that
everything before the `-[` part is the `root` of the utility. This also
means that this should exist in the design system as-is.
If we use `findRoot` instead, it means that `bg-red-[#0088cc]` would
also just parse fine and we don't want.
* small refactor: define `important` and `negative` directly
It's not necessary to track state in a state object. Let's use the
variables directly.
* small refactor: use return on same line for consistency
* add test that verifies that parsing `bg-` should not work
* move `value === ''` check up
The value doesn't change anymore, which means we can discard early and
don't need to start creating candidate objects.
* adjust comment with example
* update changelog
* Use `initial` for `@property` fallbacks instead of ` `
* Update changelog
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
* Support combining arbitrary shadows without a color with shadow color utilities
* Update changelog
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
* Use longform length + percentage syntax for properties
Using properties that declare the shorthand syntax `<length-percentage>` has a bug where two properties side-by-side e.g. `var(—foo)var(—bar)` invalidate the property value when it should not. Switching the `@property` definition to use the long form syntax `<length> | <percentage>` fixes this.
* Update changelog
* Update tests
* move `length` data type from `background-position` to `background-size`
This way it's backwards compatible with v3.
* sort data types
* update changelog
* make sure `length` is inferred later
Otherwise `bg-[120px]` would be inferred as `length` instead of
`position`.
In v3 this maps to `position` instead of `length`.
```css
.bg-\[120px\] {
background-position: 120px;
}
```
* add explicit test cases for `length` and `size` data types
* run `pnpm update --recursive`
* update tests to reflect lightningcss bump
It looks like it's mainly (re-)ordering properties. Not 100% sure why
though.
* Fixes exports when importing CJS form ESM file
* Build a real ESM version of the postcss plugin
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>