2026-04-29 20:48:08 +02:00
|
|
|
import { candidate, css, html, json, test, ts } from '../utils'
|
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221)
This PR enables compatibility for the `@tailwindcss/typography` and
`@tailwindcss/forms` plugins. This required the addition of new Plugin
APIs and new package exports.
## New Plugin APIs and compatibility improvements
We added support for `addComponents`, `matchComponents`, and `prefix`.
The component APIs are an alias for the utilities APIs because the
sorting in V4 is different and emitting components in a custom `@layer`
is not necessary. Since `prefix` is not supported in V4, the `prefix()`
API is currently an identity function.
```js
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
},
'.btn-blue': {
backgroundColor: '#3490dc',
color: '#fff',
'&:hover': {
backgroundColor: '#2779bd',
},
},
'.btn-red': {
backgroundColor: '#e3342f',
color: '#fff',
'&:hover': {
backgroundColor: '#cc1f1a',
},
},
})
```
The behavioral changes effect the `addUtilities` and `matchUtilities`
functions, we now:
- Allow arrays of CSS property objects to be emitted:
```js
addUtilities({
'.text-trim': [
{'text-box-trim': 'both'},
{'text-box-edge': 'cap alphabetic'},
],
})
```
- Allow arrays of utilities
```js
addUtilities([
{
'.text-trim':{
'text-box-trim': 'both',
'text-box-edge': 'cap alphabetic',
},
}
])
```
- Allow more complicated selector names
```js
addUtilities({
'.form-input, .form-select, .form-radio': {
/* styles here */
},
'.form-input::placeholder': {
/* styles here */
},
'.form-checkbox:indeterminate:checked': {
/* styles here */
}
})
```
## New `tailwindcss/color` and `tailwindcss/defaultTheme` export
To be compatible to v3, we're adding two new exports to the tailwindcss
package. These match the default theme values as defined in v3:
```ts
import colors from 'tailwindcss/colors'
console.log(colors.red[600])
```
```ts
import theme from 'tailwindcss/defaultTheme'
console.log(theme.spacing[4])
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
|
|
|
|
|
|
|
|
test(
|
Add support for matching multiple utility definitions for one candidate (#14231)
Currently if a plugin adds a utility called `duration` it will take
precedence over the built-in utilities — or any utilities with the same
name in previously included plugins. However, in v3, we emitted matches
from _all_ plugins where possible.
Take this plugin for example which adds utilities for
`animation-duration` via the `duration-*` class:
```ts
import plugin from 'tailwindcss/plugin'
export default plugin(
function ({ matchUtilities, theme }) {
matchUtilities(
{ duration: (value) => ({ animationDuration: value }) },
{ values: theme("animationDuration") },
)
},
{
theme: {
extend: {
animationDuration: ({ theme }) => ({
...theme("transitionDuration"),
}),
}
},
}
)
```
Before this PR this plugin's `duration` utility would override the
built-in `duration` utility so you'd get this for a class like
`duration-3500`:
```css
.duration-3000 {
animation-duration: 3500ms;
}
```
Now, after this PR, we'll emit rules for `transition-duration`
(Tailwind's built-in `duration-*` utility) and `animation-duration`
(from the above plugin) and you'll get this instead:
```css
.duration-3000 {
transition-duration: 3500ms;
}
.duration-3000 {
animation-duration: 3500ms;
}
```
These are output as separate rules to ensure that they can all be sorted
appropriately against other utilities.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 10:22:12 -04:00
|
|
|
'builds the `@tailwindcss/typography` plugin utilities',
|
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221)
This PR enables compatibility for the `@tailwindcss/typography` and
`@tailwindcss/forms` plugins. This required the addition of new Plugin
APIs and new package exports.
## New Plugin APIs and compatibility improvements
We added support for `addComponents`, `matchComponents`, and `prefix`.
The component APIs are an alias for the utilities APIs because the
sorting in V4 is different and emitting components in a custom `@layer`
is not necessary. Since `prefix` is not supported in V4, the `prefix()`
API is currently an identity function.
```js
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
},
'.btn-blue': {
backgroundColor: '#3490dc',
color: '#fff',
'&:hover': {
backgroundColor: '#2779bd',
},
},
'.btn-red': {
backgroundColor: '#e3342f',
color: '#fff',
'&:hover': {
backgroundColor: '#cc1f1a',
},
},
})
```
The behavioral changes effect the `addUtilities` and `matchUtilities`
functions, we now:
- Allow arrays of CSS property objects to be emitted:
```js
addUtilities({
'.text-trim': [
{'text-box-trim': 'both'},
{'text-box-edge': 'cap alphabetic'},
],
})
```
- Allow arrays of utilities
```js
addUtilities([
{
'.text-trim':{
'text-box-trim': 'both',
'text-box-edge': 'cap alphabetic',
},
}
])
```
- Allow more complicated selector names
```js
addUtilities({
'.form-input, .form-select, .form-radio': {
/* styles here */
},
'.form-input::placeholder': {
/* styles here */
},
'.form-checkbox:indeterminate:checked': {
/* styles here */
}
})
```
## New `tailwindcss/color` and `tailwindcss/defaultTheme` export
To be compatible to v3, we're adding two new exports to the tailwindcss
package. These match the default theme values as defined in v3:
```ts
import colors from 'tailwindcss/colors'
console.log(colors.red[600])
```
```ts
import theme from 'tailwindcss/defaultTheme'
console.log(theme.spacing[4])
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"@tailwindcss/typography": "^0.5.14",
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`
|
2025-02-21 15:02:07 +01:00
|
|
|
<div className="prose prose-stone prose-invert">
|
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221)
This PR enables compatibility for the `@tailwindcss/typography` and
`@tailwindcss/forms` plugins. This required the addition of new Plugin
APIs and new package exports.
## New Plugin APIs and compatibility improvements
We added support for `addComponents`, `matchComponents`, and `prefix`.
The component APIs are an alias for the utilities APIs because the
sorting in V4 is different and emitting components in a custom `@layer`
is not necessary. Since `prefix` is not supported in V4, the `prefix()`
API is currently an identity function.
```js
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
},
'.btn-blue': {
backgroundColor: '#3490dc',
color: '#fff',
'&:hover': {
backgroundColor: '#2779bd',
},
},
'.btn-red': {
backgroundColor: '#e3342f',
color: '#fff',
'&:hover': {
backgroundColor: '#cc1f1a',
},
},
})
```
The behavioral changes effect the `addUtilities` and `matchUtilities`
functions, we now:
- Allow arrays of CSS property objects to be emitted:
```js
addUtilities({
'.text-trim': [
{'text-box-trim': 'both'},
{'text-box-edge': 'cap alphabetic'},
],
})
```
- Allow arrays of utilities
```js
addUtilities([
{
'.text-trim':{
'text-box-trim': 'both',
'text-box-edge': 'cap alphabetic',
},
}
])
```
- Allow more complicated selector names
```js
addUtilities({
'.form-input, .form-select, .form-radio': {
/* styles here */
},
'.form-input::placeholder': {
/* styles here */
},
'.form-checkbox:indeterminate:checked': {
/* styles here */
}
})
```
## New `tailwindcss/color` and `tailwindcss/defaultTheme` export
To be compatible to v3, we're adding two new exports to the tailwindcss
package. These match the default theme values as defined in v3:
```ts
import colors from 'tailwindcss/colors'
console.log(colors.red[600])
```
```ts
import theme from 'tailwindcss/defaultTheme'
console.log(theme.spacing[4])
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
|
|
|
<h1>Headline</h1>
|
|
|
|
|
<p>
|
|
|
|
|
Until now, trying to style an article, document, or blog post with Tailwind has been a
|
|
|
|
|
tedious task that required a keen eye for typography and a lot of complex custom CSS.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss';
|
|
|
|
|
@plugin '@tailwindcss/typography';
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-02-21 15:02:07 +01:00
|
|
|
async ({ fs, exec, expect }) => {
|
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221)
This PR enables compatibility for the `@tailwindcss/typography` and
`@tailwindcss/forms` plugins. This required the addition of new Plugin
APIs and new package exports.
## New Plugin APIs and compatibility improvements
We added support for `addComponents`, `matchComponents`, and `prefix`.
The component APIs are an alias for the utilities APIs because the
sorting in V4 is different and emitting components in a custom `@layer`
is not necessary. Since `prefix` is not supported in V4, the `prefix()`
API is currently an identity function.
```js
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
},
'.btn-blue': {
backgroundColor: '#3490dc',
color: '#fff',
'&:hover': {
backgroundColor: '#2779bd',
},
},
'.btn-red': {
backgroundColor: '#e3342f',
color: '#fff',
'&:hover': {
backgroundColor: '#cc1f1a',
},
},
})
```
The behavioral changes effect the `addUtilities` and `matchUtilities`
functions, we now:
- Allow arrays of CSS property objects to be emitted:
```js
addUtilities({
'.text-trim': [
{'text-box-trim': 'both'},
{'text-box-edge': 'cap alphabetic'},
],
})
```
- Allow arrays of utilities
```js
addUtilities([
{
'.text-trim':{
'text-box-trim': 'both',
'text-box-edge': 'cap alphabetic',
},
}
])
```
- Allow more complicated selector names
```js
addUtilities({
'.form-input, .form-select, .form-radio': {
/* styles here */
},
'.form-input::placeholder': {
/* styles here */
},
'.form-checkbox:indeterminate:checked': {
/* styles here */
}
})
```
## New `tailwindcss/color` and `tailwindcss/defaultTheme` export
To be compatible to v3, we're adding two new exports to the tailwindcss
package. These match the default theme values as defined in v3:
```ts
import colors from 'tailwindcss/colors'
console.log(colors.red[600])
```
```ts
import theme from 'tailwindcss/defaultTheme'
console.log(theme.spacing[4])
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
|
|
|
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
|
|
|
|
|
|
2025-02-21 15:02:07 +01:00
|
|
|
// Verify that `prose-stone` is defined before `prose-invert`
|
|
|
|
|
{
|
|
|
|
|
let contents = await fs.read('dist/out.css')
|
|
|
|
|
let proseInvertIdx = contents.indexOf('.prose-invert')
|
|
|
|
|
let proseStoneIdx = contents.indexOf('.prose-stone')
|
|
|
|
|
|
|
|
|
|
expect(proseStoneIdx).toBeLessThan(proseInvertIdx)
|
|
|
|
|
}
|
|
|
|
|
|
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221)
This PR enables compatibility for the `@tailwindcss/typography` and
`@tailwindcss/forms` plugins. This required the addition of new Plugin
APIs and new package exports.
## New Plugin APIs and compatibility improvements
We added support for `addComponents`, `matchComponents`, and `prefix`.
The component APIs are an alias for the utilities APIs because the
sorting in V4 is different and emitting components in a custom `@layer`
is not necessary. Since `prefix` is not supported in V4, the `prefix()`
API is currently an identity function.
```js
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
},
'.btn-blue': {
backgroundColor: '#3490dc',
color: '#fff',
'&:hover': {
backgroundColor: '#2779bd',
},
},
'.btn-red': {
backgroundColor: '#e3342f',
color: '#fff',
'&:hover': {
backgroundColor: '#cc1f1a',
},
},
})
```
The behavioral changes effect the `addUtilities` and `matchUtilities`
functions, we now:
- Allow arrays of CSS property objects to be emitted:
```js
addUtilities({
'.text-trim': [
{'text-box-trim': 'both'},
{'text-box-edge': 'cap alphabetic'},
],
})
```
- Allow arrays of utilities
```js
addUtilities([
{
'.text-trim':{
'text-box-trim': 'both',
'text-box-edge': 'cap alphabetic',
},
}
])
```
- Allow more complicated selector names
```js
addUtilities({
'.form-input, .form-select, .form-radio': {
/* styles here */
},
'.form-input::placeholder': {
/* styles here */
},
'.form-checkbox:indeterminate:checked': {
/* styles here */
}
})
```
## New `tailwindcss/color` and `tailwindcss/defaultTheme` export
To be compatible to v3, we're adding two new exports to the tailwindcss
package. These match the default theme values as defined in v3:
```ts
import colors from 'tailwindcss/colors'
console.log(colors.red[600])
```
```ts
import theme from 'tailwindcss/defaultTheme'
console.log(theme.spacing[4])
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
|
|
|
await fs.expectFileToContain('dist/out.css', [
|
|
|
|
|
candidate`prose`,
|
Add `list`, `compound`, and `complex` Selector nodes (#20088)
This PR introduces a few more nodes in the `SelectorParser`:
- A `list` node
- A `complex` node
- A `compound` node
These names are closer to the CSS Selector AST names
(https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Selectors/Selector_structure),
and are also used in other libraries.
The problem today is that there are situations where we parse a selector
like: `#a.b > .c, .d` as:
```ts
[
{ kind: 'selector', value: '#a' },
{ kind: 'selector', value: '.b' },
{ kind: 'combinator', value: ' > ' },
{ kind: 'selector', value: '.c' },
{ kind: 'separator', value: ', ' },
{ kind: 'selector', value: '.d' }
]
```
Which is a very simple structure, but this contains a flaw that is
annoying to deal with in practice: In order to determine that we are
dealing with multiple selectors, we have to loop through the nodes and
see if a separator occurs somewhere.
The other fun thing is that we already know the difference between
selectors, combinators and separators. So if we tweak this structure a
little bit during parsing, then we can answer the question from above in
a much simpler way:
With this PR, we will parse the selector as:
```ts
[
{
kind: 'list',
nodes: [
{
kind: 'complex',
nodes: [
{
kind: 'compound',
nodes: [
{ kind: 'selector', value: '#a' },
{ kind: 'selector', value: '.b' }
]
},
{ kind: 'combinator', value: '>' },
{ kind: 'selector', value: '.c' }
]
},
{ kind: 'selector', value: '.d' }
]
}
]
```
It definitely looks more complex, but now that we have a `list` node, we
already know that we are dealing with multiple selectors.
If you squint your eyes, in the inner part there is a `compound`
selector. This is essentially a node where each sub-node can be squished
together with no spaces whatsoever.
The `complex` selector is there just to group everything together. In
other tools, a complex selector is often represented as:
```ts
{
kind: 'complex',
combinator: '>',
lhs: { … },
rhs: { … },
}
```
While I want to have the concept of a `complex` node, I didn't go with
this syntax just because I want to keep the concept of `nodes` which
means that we don't need any special handling when using `walk` (which
loops over `.nodes` internally).
The reason this complex node exists is because otherwise you would end
up with this structure:
```ts
[
{
kind: 'list',
nodes: [
{
kind: 'compound',
nodes: [
{ kind: 'selector', value: '#a' },
{ kind: 'selector', value: '.b' }
]
},
{ kind: 'combinator', value: '>' },
{ kind: 'selector', value: '.c' }
{ kind: 'selector', value: '.d' }
]
}
]
```
But if you look at the `list` node now, it's not clear that we are
dealing with `2` selectors since there are 4 nodes. We could solve this
by re-introducing the separator node (`,`). The fact that the `list`
exists tells us that we're dealing with `n` selectors. But to know which
selectors we're dealing with, then we have to look for that `,` node
again, which introduces the original problem.
This is just an internal refactor to make future changes easier.
## Test plan
1. Everything still works as expected (all tests pass)
2. No public API breaking changes, this parser was never exposed
2026-05-20 15:04:05 +02:00
|
|
|
':where(h1):not(:where([class~="not-prose"], [class~="not-prose"] *))',
|
|
|
|
|
':where(tbody td, tfoot td):not(:where([class~="not-prose"], [class~="not-prose"] *))',
|
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221)
This PR enables compatibility for the `@tailwindcss/typography` and
`@tailwindcss/forms` plugins. This required the addition of new Plugin
APIs and new package exports.
## New Plugin APIs and compatibility improvements
We added support for `addComponents`, `matchComponents`, and `prefix`.
The component APIs are an alias for the utilities APIs because the
sorting in V4 is different and emitting components in a custom `@layer`
is not necessary. Since `prefix` is not supported in V4, the `prefix()`
API is currently an identity function.
```js
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
},
'.btn-blue': {
backgroundColor: '#3490dc',
color: '#fff',
'&:hover': {
backgroundColor: '#2779bd',
},
},
'.btn-red': {
backgroundColor: '#e3342f',
color: '#fff',
'&:hover': {
backgroundColor: '#cc1f1a',
},
},
})
```
The behavioral changes effect the `addUtilities` and `matchUtilities`
functions, we now:
- Allow arrays of CSS property objects to be emitted:
```js
addUtilities({
'.text-trim': [
{'text-box-trim': 'both'},
{'text-box-edge': 'cap alphabetic'},
],
})
```
- Allow arrays of utilities
```js
addUtilities([
{
'.text-trim':{
'text-box-trim': 'both',
'text-box-edge': 'cap alphabetic',
},
}
])
```
- Allow more complicated selector names
```js
addUtilities({
'.form-input, .form-select, .form-radio': {
/* styles here */
},
'.form-input::placeholder': {
/* styles here */
},
'.form-checkbox:indeterminate:checked': {
/* styles here */
}
})
```
## New `tailwindcss/color` and `tailwindcss/defaultTheme` export
To be compatible to v3, we're adding two new exports to the tailwindcss
package. These match the default theme values as defined in v3:
```ts
import colors from 'tailwindcss/colors'
console.log(colors.red[600])
```
```ts
import theme from 'tailwindcss/defaultTheme'
console.log(theme.spacing[4])
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
Ensure `@variant` can be used in JS based APIs (#20252)
This PR doesn't fix any issues, but it does add an integration test (as
a regression test) to make sure that `@variant` with default variants
and custom variants can be used inside of JS based plugin APIs.
In Tailwind CSS v4.3.1 we introduced a PR that handles `@variant` in the
`addBase` Plugin API
(https://github.com/tailwindlabs/tailwindcss/pull/19480). This was a bit
of an older PR, but the tests made sense, so it was merged.
However, by introducing that PR, we introduced a bug that the `@variant`
was handled too early. If you added custom variants later _and_ used it
in the `addBase`, then you would get an error since the variant isn't
available (yet).
That issue was fixed by
https://github.com/tailwindlabs/tailwindcss/pull/20247
Now the question remains, why did we even have the original PR when it
already worked?
The use case we had was using `@variant` as part of the
`@tailwindcss/typography` plugin configuration for one of our templates.
I was indeed able to reproduce the issue where `@variant lg` was seen in
the output CSS file.
Turns out that this template was using `@tailwindcss/typography` +
`@variant` in the configuration, but it was also using Tailwind CSS
v4.1.15.
Upgrading to the latest version automagically fixed the issue we had.
This is also the behavior you can see in the integration test.
The correct behavior was introduced in an even older PR
https://github.com/tailwindlabs/tailwindcss/pull/19263
All that said, everything should work in the next release related to
`@variant` usages inside JS based APIs.
**Tiny improvement**
While debugging what's going on, I noticed that we looped over the AST
to get some nodes out and we did that twice. This PR also improves that
by re-using the same list of nodes instead of computing it twice. This
won't have a huge impact, but it happened while compiling every single
utility which is not ideal.
## Test plan
1. All tests should pass
2. I can't see `@variant` in the output CSS file
Input:
<img width="655" height="323" alt="image"
src="https://github.com/user-attachments/assets/20c8d524-3575-488c-b0c0-5c4669f37dc7"
/>
Before:
<img width="477" height="175" alt="image"
src="https://github.com/user-attachments/assets/045e2086-1d4b-487c-96ff-676351412935"
/>
After:
<img width="484" height="175" alt="image"
src="https://github.com/user-attachments/assets/24d950b1-7c28-40f8-96bc-81b38be0e7a3"
/>
2026-06-17 15:23:20 +02:00
|
|
|
test(
|
|
|
|
|
'builds the `@tailwindcss/typography` plugin utilities with `@variant` usages',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"@tailwindcss/typography": "^0.5.14",
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`
|
|
|
|
|
<div className="prose prose-custom">
|
|
|
|
|
<h1>Headline</h1>
|
|
|
|
|
<p>
|
|
|
|
|
Until now, trying to style an article, document, or blog post with Tailwind has been a
|
|
|
|
|
tedious task that required a keen eye for typography and a lot of complex custom CSS.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
@theme {
|
|
|
|
|
--breakpoint-sm: 640px;
|
|
|
|
|
}
|
|
|
|
|
@plugin '@tailwindcss/typography';
|
|
|
|
|
@config '../tailwind.config.js';
|
|
|
|
|
@custom-variant custom (&.custom);
|
|
|
|
|
`,
|
|
|
|
|
'tailwind.config.js': ts`
|
|
|
|
|
module.exports = {
|
|
|
|
|
theme: {
|
|
|
|
|
typography: ({ theme }) => ({
|
|
|
|
|
custom: {
|
|
|
|
|
css: {
|
|
|
|
|
hr: {
|
|
|
|
|
'--x': '1',
|
|
|
|
|
'@variant sm:custom': {
|
|
|
|
|
'--x': '2',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, exec, expect }) => {
|
|
|
|
|
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
|
|
|
|
|
|
|
|
|
|
// We don't want to see `@variant` in the output
|
|
|
|
|
let contents = await fs.read('dist/out.css')
|
|
|
|
|
expect(contents).not.toContain('@variant')
|
|
|
|
|
|
|
|
|
|
expect(await fs.dumpFiles('dist/out.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- dist/out.css ---
|
Handle CSS nesting natively (#20124)
This PR introduces a new feature where we will be handling the CSS
nesting ourselves.
We currently still rely on Lightning CSS in most places. But there are
situations where we don't use Lightning CSS out of the box:
1. During development, typically optimization/minification isn't setup
2. In places where it isn't as easy to run Lightning CSS such as in
`@tailwindcss/browser` or in Tailwind Play.
We handle CSS nesting in a single pass over the AST by tracking some
information as we go. It's not the most complex code, but there are some
tricky parts to make this happen in an efficient way, especially for the
few additional optimizations we handle.
While going over the AST, we will only emit CSS the moment we see
declarations or comments. This also means that this has a fun side
effect of removing CSS that ends up with empty nodes automatically.
(Caveat: there are exceptions for body-less rules such as `@layer foo;`
or `@charset "UTF-8";)
This also allowed us to do some cleanup in `optimizeAst` that tried to
do this as well, but now this will be handled by the code that handles
nesting automatically. Which is preferred because the version in
`optimizeAst` mutated the AST.
This also contains some optimizations where we merge adjacent at-rules
(with the same name / params), and adjacent rules with the same
selector, and get rid of declarations that are duplicated in a node.
(Caveat: there are exceptions, in case of `@font-family { … }` where we
don't want to merge them)
~~To ensure that this implementation is correct, I also added an oracle
implementation in the tests. This implementation does multiple passes
over the AST, because it does each step one by one, with minimal code.
Each step contains comments with examples to see what's happening in
that step. We then test the optimized version against this.~~ Once the
implementation was in place, and all the tests were passing, then I
deleted the oracle implementation. That way we don't have to keep it in
sync all the time.
While handling the nesting, we have to make sure that `&` exists and if
we replace it with a parent selector that we do use `:is(…)` semantics.
This means that:
```css
.foo {
&:hover {
color: red;
}
}
```
Becomes:
```css
:is(.foo):hover {
color: red;
}
```
We then also make sure that we optimize the selector by removing the
unnecessary `:is(…)` wrappers, but only if they were introduced by the
nesting logic. If _you_ wrote `:is(…)` in your CSS, we won't touch it.
If you look at the commits, the first thing we did is remove the
optimization step from Lightning CSS in the tests. Then we enabled our
CSS nesting handling code. This allows us to see the effect of the
changes we are making. At the end, we re-enabled Lightning CSS.
For now, this PR will be a step that happens before Lightning CSS is
executed, while still using Lightning CSS. But now this step will also
always happen in places where we don't use Lightning CSS at all.
This should not result in any breaking changes. It could result in
changed CSS output in environments where Lightning CSS isn't used. In
environments where it is being used, then there could be some
differences related to some selectors but they should result in the same
behavior with the same specificity.
While testing things, I noticed that there are some missed opportunities
for performance related to how we extract variables from declaration
values. I want to tackle `optimizeAst` in future PRs to make it simpler,
more performant, and maybe even merge it with the CSS nesting handling.
As part of testing this, I tested it against the tailwindcss.com
codebase which contains a lot of CSS (807.67 KB, 18 174 AST nodes)
because almost every utility is being used in examples.
The oracle implementation is rather slow:
```
[131.59ms] ↳ oracle (step by step)
[129.23ms] ↳ handleNesting(…)
[ 2.32ms] ↳ toCss(…)
```
But the final code is much faster (`<15ms`):
```
[ 10.11ms] ↳ hand written (single pass)
[ 8.40ms] ↳ handleNesting(…)
[ 1.67ms] ↳ toCss(…)
```
In contrast, Lightning CSS takes: `[ 37.48ms] Optimized by Lightning
CSS`
One interesting thing to notice is that in big projects, this could add
`10ms` to the build, but Lightning CSS would then take less time to
process, which results in a no-op with better output.
One thing to keep in mind here is that Lightning CSS does more things,
such as normalizing values, handling vendor prefixes, CSS nesting, etc.
<details>
<summary>Some notes on how the algorithm works:</summary>
### The basic idea
When you have CSS that looks this:
```css
.foo {
.bar {
color: red;
}
}
```
Then the AST looks like this:
```
[
{
kind: 'rule',
selector: '.foo',
nodes: [
{
kind: 'rule',
selector: '.bar',
nodes: [
{
kind: 'declaration',
property: 'color',
value: 'red',
important: false
}
]
}
]
}
]
```
When we walk this tree, and we encounter a `rule`, then we will track
the selector on a stack. When we are done walking over the rule, then we
will pop the selector from the stack. This means that the top-most
selector on the stack will always be the parent selector.
```ts
let selectorStack = []
walk(ast, {
enter(node) {
selectorStack.push(node.selector)
},
exit(node) {
selectorStack.pop()
},
})
```
The moment we encounter a `rule`, and if a previous rule was seen, then
we push the `selector` of the rule onto the stack, but in a way that the
`&` is already replaced by the selector. This way, a sibling rule will
also get the already-prepared parent selector.
The simple version looks like this:
```ts
walk(ast, {
enter(node) {
// In the real code we properly handle `&` replacement, and make sure that
// parent selector is prepended if there is no `&` used in the selector of the
// node.
let selector =
selectorStack.length > 0
// At this point, we don't optimize anything related to the selector yet
? node.selector.replaceAll('&', `:is(${selectorStack.at(-1)})`)
: node.selector
selectorStack.push(selector)
},
exit(node) {
selectorStack.pop()
},
})
```
So far we aren't doing much yet, but the interesting part is when we
encounter a `declaration` (or a `comment`). The moment we see any of
those, then will we emit a node with the information from the
`selectorStack`.
We then also track the last node's `nodes` we created such that we can
push more declarations into it as a shortcut.
```ts
let result: AstNode[] = []
let nodes: AstNode[] | null = null
walk(ast, {
enter(node) {
if (node.kind === 'declaration') {
// `nodes` is available, nothing special to do
if (nodes) {
nodes.push(node)
return
}
// Track new nodes
let nodes = [node]
// Create a new node with a reference to `nodes` for future declarations
let newNode = rule(selectorStack.at(-1), nodes)
result.push(newNode)
}
},
})
```
The last important part is that whenever we see a new `rule`, then we
have to reset that `nodes` tracking variable such that we can create a
fresh node the next time we see a declaration.
For the `at-rules`, something similar happens but they are tracked in a
similar but separate stack. The idea there is that we can then wrap
those `at-rules` around the `newNode` we create. That way the at-rules
naturally float to the top.
I can keep going here, but I think if you're interested in this, then
you could go over the commits in this PR, or you can look at the
`ast.ts` implementation directly to see what's going on.
</details>
## Test plan
1. Existing tests should pass
2. New tests have been added to test the flattening of nested CSS
2026-07-07 18:28:41 +02:00
|
|
|
.prose-custom :where(hr):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
|
|
|
|
--x: 1;
|
|
|
|
|
@media (width >= 640px) {
|
|
|
|
|
&.custom {
|
|
|
|
|
--x: 2;
|
Ensure `@variant` can be used in JS based APIs (#20252)
This PR doesn't fix any issues, but it does add an integration test (as
a regression test) to make sure that `@variant` with default variants
and custom variants can be used inside of JS based plugin APIs.
In Tailwind CSS v4.3.1 we introduced a PR that handles `@variant` in the
`addBase` Plugin API
(https://github.com/tailwindlabs/tailwindcss/pull/19480). This was a bit
of an older PR, but the tests made sense, so it was merged.
However, by introducing that PR, we introduced a bug that the `@variant`
was handled too early. If you added custom variants later _and_ used it
in the `addBase`, then you would get an error since the variant isn't
available (yet).
That issue was fixed by
https://github.com/tailwindlabs/tailwindcss/pull/20247
Now the question remains, why did we even have the original PR when it
already worked?
The use case we had was using `@variant` as part of the
`@tailwindcss/typography` plugin configuration for one of our templates.
I was indeed able to reproduce the issue where `@variant lg` was seen in
the output CSS file.
Turns out that this template was using `@tailwindcss/typography` +
`@variant` in the configuration, but it was also using Tailwind CSS
v4.1.15.
Upgrading to the latest version automagically fixed the issue we had.
This is also the behavior you can see in the integration test.
The correct behavior was introduced in an even older PR
https://github.com/tailwindlabs/tailwindcss/pull/19263
All that said, everything should work in the next release related to
`@variant` usages inside JS based APIs.
**Tiny improvement**
While debugging what's going on, I noticed that we looped over the AST
to get some nodes out and we did that twice. This PR also improves that
by re-using the same list of nodes instead of computing it twice. This
won't have a huge impact, but it happened while compiling every single
utility which is not ideal.
## Test plan
1. All tests should pass
2. I can't see `@variant` in the output CSS file
Input:
<img width="655" height="323" alt="image"
src="https://github.com/user-attachments/assets/20c8d524-3575-488c-b0c0-5c4669f37dc7"
/>
Before:
<img width="477" height="175" alt="image"
src="https://github.com/user-attachments/assets/045e2086-1d4b-487c-96ff-676351412935"
/>
After:
<img width="484" height="175" alt="image"
src="https://github.com/user-attachments/assets/24d950b1-7c28-40f8-96bc-81b38be0e7a3"
/>
2026-06-17 15:23:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221)
This PR enables compatibility for the `@tailwindcss/typography` and
`@tailwindcss/forms` plugins. This required the addition of new Plugin
APIs and new package exports.
## New Plugin APIs and compatibility improvements
We added support for `addComponents`, `matchComponents`, and `prefix`.
The component APIs are an alias for the utilities APIs because the
sorting in V4 is different and emitting components in a custom `@layer`
is not necessary. Since `prefix` is not supported in V4, the `prefix()`
API is currently an identity function.
```js
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
},
'.btn-blue': {
backgroundColor: '#3490dc',
color: '#fff',
'&:hover': {
backgroundColor: '#2779bd',
},
},
'.btn-red': {
backgroundColor: '#e3342f',
color: '#fff',
'&:hover': {
backgroundColor: '#cc1f1a',
},
},
})
```
The behavioral changes effect the `addUtilities` and `matchUtilities`
functions, we now:
- Allow arrays of CSS property objects to be emitted:
```js
addUtilities({
'.text-trim': [
{'text-box-trim': 'both'},
{'text-box-edge': 'cap alphabetic'},
],
})
```
- Allow arrays of utilities
```js
addUtilities([
{
'.text-trim':{
'text-box-trim': 'both',
'text-box-edge': 'cap alphabetic',
},
}
])
```
- Allow more complicated selector names
```js
addUtilities({
'.form-input, .form-select, .form-radio': {
/* styles here */
},
'.form-input::placeholder': {
/* styles here */
},
'.form-checkbox:indeterminate:checked': {
/* styles here */
}
})
```
## New `tailwindcss/color` and `tailwindcss/defaultTheme` export
To be compatible to v3, we're adding two new exports to the tailwindcss
package. These match the default theme values as defined in v3:
```ts
import colors from 'tailwindcss/colors'
console.log(colors.red[600])
```
```ts
import theme from 'tailwindcss/defaultTheme'
console.log(theme.spacing[4])
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
|
|
|
test(
|
Add support for matching multiple utility definitions for one candidate (#14231)
Currently if a plugin adds a utility called `duration` it will take
precedence over the built-in utilities — or any utilities with the same
name in previously included plugins. However, in v3, we emitted matches
from _all_ plugins where possible.
Take this plugin for example which adds utilities for
`animation-duration` via the `duration-*` class:
```ts
import plugin from 'tailwindcss/plugin'
export default plugin(
function ({ matchUtilities, theme }) {
matchUtilities(
{ duration: (value) => ({ animationDuration: value }) },
{ values: theme("animationDuration") },
)
},
{
theme: {
extend: {
animationDuration: ({ theme }) => ({
...theme("transitionDuration"),
}),
}
},
}
)
```
Before this PR this plugin's `duration` utility would override the
built-in `duration` utility so you'd get this for a class like
`duration-3500`:
```css
.duration-3000 {
animation-duration: 3500ms;
}
```
Now, after this PR, we'll emit rules for `transition-duration`
(Tailwind's built-in `duration-*` utility) and `animation-duration`
(from the above plugin) and you'll get this instead:
```css
.duration-3000 {
transition-duration: 3500ms;
}
.duration-3000 {
animation-duration: 3500ms;
}
```
These are output as separate rules to ensure that they can all be sorted
appropriately against other utilities.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 10:22:12 -04:00
|
|
|
'builds the `@tailwindcss/forms` plugin utilities',
|
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221)
This PR enables compatibility for the `@tailwindcss/typography` and
`@tailwindcss/forms` plugins. This required the addition of new Plugin
APIs and new package exports.
## New Plugin APIs and compatibility improvements
We added support for `addComponents`, `matchComponents`, and `prefix`.
The component APIs are an alias for the utilities APIs because the
sorting in V4 is different and emitting components in a custom `@layer`
is not necessary. Since `prefix` is not supported in V4, the `prefix()`
API is currently an identity function.
```js
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
},
'.btn-blue': {
backgroundColor: '#3490dc',
color: '#fff',
'&:hover': {
backgroundColor: '#2779bd',
},
},
'.btn-red': {
backgroundColor: '#e3342f',
color: '#fff',
'&:hover': {
backgroundColor: '#cc1f1a',
},
},
})
```
The behavioral changes effect the `addUtilities` and `matchUtilities`
functions, we now:
- Allow arrays of CSS property objects to be emitted:
```js
addUtilities({
'.text-trim': [
{'text-box-trim': 'both'},
{'text-box-edge': 'cap alphabetic'},
],
})
```
- Allow arrays of utilities
```js
addUtilities([
{
'.text-trim':{
'text-box-trim': 'both',
'text-box-edge': 'cap alphabetic',
},
}
])
```
- Allow more complicated selector names
```js
addUtilities({
'.form-input, .form-select, .form-radio': {
/* styles here */
},
'.form-input::placeholder': {
/* styles here */
},
'.form-checkbox:indeterminate:checked': {
/* styles here */
}
})
```
## New `tailwindcss/color` and `tailwindcss/defaultTheme` export
To be compatible to v3, we're adding two new exports to the tailwindcss
package. These match the default theme values as defined in v3:
```ts
import colors from 'tailwindcss/colors'
console.log(colors.red[600])
```
```ts
import theme from 'tailwindcss/defaultTheme'
console.log(theme.spacing[4])
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"@tailwindcss/forms": "^0.5.7",
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`
|
|
|
|
|
<input type="text" class="form-input" />
|
|
|
|
|
<textarea class="form-textarea"></textarea>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss';
|
|
|
|
|
@plugin '@tailwindcss/forms';
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, exec }) => {
|
|
|
|
|
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('dist/out.css', [
|
|
|
|
|
//
|
|
|
|
|
candidate`form-input`,
|
|
|
|
|
candidate`form-textarea`,
|
|
|
|
|
])
|
|
|
|
|
await fs.expectFileNotToContain('dist/out.css', [
|
|
|
|
|
//
|
|
|
|
|
candidate`form-radio`,
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
Add support for matching multiple utility definitions for one candidate (#14231)
Currently if a plugin adds a utility called `duration` it will take
precedence over the built-in utilities — or any utilities with the same
name in previously included plugins. However, in v3, we emitted matches
from _all_ plugins where possible.
Take this plugin for example which adds utilities for
`animation-duration` via the `duration-*` class:
```ts
import plugin from 'tailwindcss/plugin'
export default plugin(
function ({ matchUtilities, theme }) {
matchUtilities(
{ duration: (value) => ({ animationDuration: value }) },
{ values: theme("animationDuration") },
)
},
{
theme: {
extend: {
animationDuration: ({ theme }) => ({
...theme("transitionDuration"),
}),
}
},
}
)
```
Before this PR this plugin's `duration` utility would override the
built-in `duration` utility so you'd get this for a class like
`duration-3500`:
```css
.duration-3000 {
animation-duration: 3500ms;
}
```
Now, after this PR, we'll emit rules for `transition-duration`
(Tailwind's built-in `duration-*` utility) and `animation-duration`
(from the above plugin) and you'll get this instead:
```css
.duration-3000 {
transition-duration: 3500ms;
}
.duration-3000 {
animation-duration: 3500ms;
}
```
These are output as separate rules to ensure that they can all be sorted
appropriately against other utilities.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 10:22:12 -04:00
|
|
|
|
Support plugin options in CSS (#14264)
Builds on #14239 — that PR needs to be merged first.
This PR allows plugins defined with `plugin.withOptions` to receive
options in CSS when using `@plugin` as long as the options are simple
key/value pairs.
For example, the following is now valid and will include the forms
plugin with only the base styles enabled:
```css
@plugin "@tailwindcss/forms" {
strategy: base;
}
```
We handle `null`, `true`, `false`, and numeric values as expected and
will convert them to their JavaScript equivalents. Comma separated
values are turned into arrays. All other values are converted to
strings.
For example, in the following plugin definition, the options that are
passed to the plugin will be the correct types:
- `debug` will be the boolean value `true`
- `threshold` will be the number `0.5`
- `message` will be the string `"Hello world"`
- `features` will be the array `["base", "responsive"]`
```css
@plugin "my-plugin" {
debug: false;
threshold: 0.5;
message: Hello world;
features: base, responsive;
}
```
If you need to pass a number or boolean value as a string, you can do so
by wrapping the value in quotes:
```css
@plugin "my-plugin" {
debug: "false";
threshold: "0.5";
message: "Hello world";
}
```
When duplicate options are encountered the last value wins:
```css
@plugin "my-plugin" {
message: Hello world;
message: Hello plugin; /* this will be the value of `message` */
}
```
It's important to note that this feature is **only available for plugins
defined with `plugin.withOptions`**. If you try to pass options to a
plugin that doesn't support them, you'll get an error message when
building:
```css
@plugin "my-plugin" {
debug: false;
threshold: 0.5;
}
/* Error: The plugin "my-plugin" does not accept options */
```
Additionally, if you try to pass in more complex values like objects or
selectors you'll get an error message:
```css
@plugin "my-plugin" {
color: { red: 100; green: 200; blue: 300 };
}
/* Error: Objects are not supported in `@plugin` options. */
```
```css
@plugin "my-plugin" {
.some-selector > * {
primary: "blue";
secondary: "green";
}
}
/* Error: `@plugin` can only contain declarations. */
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-09-02 12:49:09 -04:00
|
|
|
test(
|
|
|
|
|
'builds the `@tailwindcss/forms` plugin utilities (with options)',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"@tailwindcss/forms": "^0.5.7",
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`
|
|
|
|
|
<input type="text" class="form-input" />
|
|
|
|
|
<textarea class="form-textarea"></textarea>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss';
|
|
|
|
|
@plugin '@tailwindcss/forms' {
|
|
|
|
|
strategy: base;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, exec }) => {
|
|
|
|
|
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('dist/out.css', [
|
|
|
|
|
//
|
|
|
|
|
`::-webkit-date-and-time-value`,
|
2025-12-17 22:27:50 -05:00
|
|
|
`input:where([type='checkbox']):indeterminate`,
|
Support plugin options in CSS (#14264)
Builds on #14239 — that PR needs to be merged first.
This PR allows plugins defined with `plugin.withOptions` to receive
options in CSS when using `@plugin` as long as the options are simple
key/value pairs.
For example, the following is now valid and will include the forms
plugin with only the base styles enabled:
```css
@plugin "@tailwindcss/forms" {
strategy: base;
}
```
We handle `null`, `true`, `false`, and numeric values as expected and
will convert them to their JavaScript equivalents. Comma separated
values are turned into arrays. All other values are converted to
strings.
For example, in the following plugin definition, the options that are
passed to the plugin will be the correct types:
- `debug` will be the boolean value `true`
- `threshold` will be the number `0.5`
- `message` will be the string `"Hello world"`
- `features` will be the array `["base", "responsive"]`
```css
@plugin "my-plugin" {
debug: false;
threshold: 0.5;
message: Hello world;
features: base, responsive;
}
```
If you need to pass a number or boolean value as a string, you can do so
by wrapping the value in quotes:
```css
@plugin "my-plugin" {
debug: "false";
threshold: "0.5";
message: "Hello world";
}
```
When duplicate options are encountered the last value wins:
```css
@plugin "my-plugin" {
message: Hello world;
message: Hello plugin; /* this will be the value of `message` */
}
```
It's important to note that this feature is **only available for plugins
defined with `plugin.withOptions`**. If you try to pass options to a
plugin that doesn't support them, you'll get an error message when
building:
```css
@plugin "my-plugin" {
debug: false;
threshold: 0.5;
}
/* Error: The plugin "my-plugin" does not accept options */
```
Additionally, if you try to pass in more complex values like objects or
selectors you'll get an error message:
```css
@plugin "my-plugin" {
color: { red: 100; green: 200; blue: 300 };
}
/* Error: Objects are not supported in `@plugin` options. */
```
```css
@plugin "my-plugin" {
.some-selector > * {
primary: "blue";
secondary: "green";
}
}
/* Error: `@plugin` can only contain declarations. */
```
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-09-02 12:49:09 -04:00
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// No classes are included even though they are used in the HTML
|
|
|
|
|
// because the `base` strategy is used
|
|
|
|
|
await fs.expectFileNotToContain('dist/out.css', [
|
|
|
|
|
//
|
|
|
|
|
candidate`form-input`,
|
|
|
|
|
candidate`form-textarea`,
|
|
|
|
|
candidate`form-radio`,
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2024-11-19 15:52:06 +01:00
|
|
|
test(
|
|
|
|
|
'builds the `@tailwindcss/aspect-ratio` plugin utilities',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"@tailwindcss/aspect-ratio": "^0.4.2",
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`
|
|
|
|
|
<div class="aspect-w-16 aspect-h-9">
|
|
|
|
|
<iframe
|
|
|
|
|
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
|
|
|
|
frameborder="0"
|
|
|
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
|
|
|
allowfullscreen
|
|
|
|
|
></iframe>
|
|
|
|
|
</div>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss';
|
|
|
|
|
@plugin '@tailwindcss/aspect-ratio';
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, exec }) => {
|
|
|
|
|
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('dist/out.css', [
|
|
|
|
|
//
|
|
|
|
|
candidate`aspect-w-16`,
|
|
|
|
|
candidate`aspect-h-9`,
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
Add support for matching multiple utility definitions for one candidate (#14231)
Currently if a plugin adds a utility called `duration` it will take
precedence over the built-in utilities — or any utilities with the same
name in previously included plugins. However, in v3, we emitted matches
from _all_ plugins where possible.
Take this plugin for example which adds utilities for
`animation-duration` via the `duration-*` class:
```ts
import plugin from 'tailwindcss/plugin'
export default plugin(
function ({ matchUtilities, theme }) {
matchUtilities(
{ duration: (value) => ({ animationDuration: value }) },
{ values: theme("animationDuration") },
)
},
{
theme: {
extend: {
animationDuration: ({ theme }) => ({
...theme("transitionDuration"),
}),
}
},
}
)
```
Before this PR this plugin's `duration` utility would override the
built-in `duration` utility so you'd get this for a class like
`duration-3500`:
```css
.duration-3000 {
animation-duration: 3500ms;
}
```
Now, after this PR, we'll emit rules for `transition-duration`
(Tailwind's built-in `duration-*` utility) and `animation-duration`
(from the above plugin) and you'll get this instead:
```css
.duration-3000 {
transition-duration: 3500ms;
}
.duration-3000 {
animation-duration: 3500ms;
}
```
These are output as separate rules to ensure that they can all be sorted
appropriately against other utilities.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 10:22:12 -04:00
|
|
|
test(
|
|
|
|
|
'builds the `tailwindcss-animate` plugin utilities',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss-animate": "^1.0.7",
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`
|
|
|
|
|
<div class="animate-in fade-in zoom-in duration-350"></div>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss';
|
|
|
|
|
@plugin 'tailwindcss-animate';
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, exec }) => {
|
|
|
|
|
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('dist/out.css', [
|
|
|
|
|
candidate`animate-in`,
|
|
|
|
|
candidate`fade-in`,
|
|
|
|
|
candidate`zoom-in`,
|
|
|
|
|
candidate`duration-350`,
|
|
|
|
|
'transition-duration: 350ms',
|
|
|
|
|
'animation-duration: 350ms',
|
2024-10-22 16:34:37 +00:00
|
|
|
'@keyframes enter {',
|
Add support for matching multiple utility definitions for one candidate (#14231)
Currently if a plugin adds a utility called `duration` it will take
precedence over the built-in utilities — or any utilities with the same
name in previously included plugins. However, in v3, we emitted matches
from _all_ plugins where possible.
Take this plugin for example which adds utilities for
`animation-duration` via the `duration-*` class:
```ts
import plugin from 'tailwindcss/plugin'
export default plugin(
function ({ matchUtilities, theme }) {
matchUtilities(
{ duration: (value) => ({ animationDuration: value }) },
{ values: theme("animationDuration") },
)
},
{
theme: {
extend: {
animationDuration: ({ theme }) => ({
...theme("transitionDuration"),
}),
}
},
}
)
```
Before this PR this plugin's `duration` utility would override the
built-in `duration` utility so you'd get this for a class like
`duration-3500`:
```css
.duration-3000 {
animation-duration: 3500ms;
}
```
Now, after this PR, we'll emit rules for `transition-duration`
(Tailwind's built-in `duration-*` utility) and `animation-duration`
(from the above plugin) and you'll get this instead:
```css
.duration-3000 {
transition-duration: 3500ms;
}
.duration-3000 {
animation-duration: 3500ms;
}
```
These are output as separate rules to ensure that they can all be sorted
appropriately against other utilities.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 10:22:12 -04:00
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-04-29 20:48:08 +02:00
|
|
|
|
|
|
|
|
// https://github.com/tailwindlabs/tailwindcss/issues/15844
|
|
|
|
|
test(
|
|
|
|
|
'builds CSS with a custom plugin compiled from TypeScript',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"type": "module",
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
},
|
|
|
|
|
"devDependencies": {
|
|
|
|
|
"typescript": "^5.7.2"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'tsconfig.json': json`
|
|
|
|
|
{
|
|
|
|
|
"compilerOptions": {
|
|
|
|
|
"target": "ES2022",
|
|
|
|
|
"module": "NodeNext",
|
|
|
|
|
"moduleResolution": "NodeNext",
|
|
|
|
|
"declaration": true,
|
|
|
|
|
"composite": true,
|
|
|
|
|
"rootDir": "./src",
|
|
|
|
|
"outDir": "./.build",
|
|
|
|
|
"skipLibCheck": true
|
|
|
|
|
},
|
|
|
|
|
"include": ["src/**/*.ts"]
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`
|
|
|
|
|
<div class="test-red"></div>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss';
|
|
|
|
|
@plugin '../.build/plugin.js';
|
|
|
|
|
`,
|
|
|
|
|
'src/plugin.ts': ts`
|
|
|
|
|
import plugin from 'tailwindcss/plugin'
|
|
|
|
|
|
|
|
|
|
export const typedPlugin = plugin(() => {
|
|
|
|
|
return ({ matchComponents }) => {
|
|
|
|
|
matchComponents(
|
|
|
|
|
{
|
|
|
|
|
test: (content: string) => ({
|
|
|
|
|
color: content,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
values: {
|
|
|
|
|
red: 'red',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export default plugin(({ matchComponents }) => {
|
|
|
|
|
matchComponents(
|
|
|
|
|
{
|
|
|
|
|
test: (content: string) => ({
|
|
|
|
|
color: content,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
values: {
|
|
|
|
|
red: 'red',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, exec }) => {
|
|
|
|
|
// We expect that these commands don't crash:
|
|
|
|
|
await exec('pnpm tsc -b')
|
|
|
|
|
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('dist/out.css', [candidate`test-red`])
|
|
|
|
|
},
|
|
|
|
|
)
|