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>
* ensure we handle strings as-in
When encountering strings when using `segment` we didn't really treat
them as actual strings. This means that if you used any parens,
brackets, or curlies then we wanted them to be properly balanced.
This should not be the case, whenever we encounter a string, we want to
consume it as-is and don't want to worry about bracket balancing. We
will now consume it until the end of the string (and make sure that
escaped closing quotes are not seen as real closing quotes).
* update changelog
* drop unnecessary test
Already had this test
* ensure we utilities and variants defined
* add example test that parses with unbalanced brackets inside quotes
* improve changelog entry
* hoist comment
* Allow hyphen character in regex pattern to use support queries as is
* Update variants.test.ts
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
* remove automatic `var(…)` injection
There are a few properties that use "dashed-ident" values, this means
that you can use `--my-thing` as-is without the `var(…)` around it.
This causes issues because we are now injecting a `var(…)` where it's not
needed.
One potential solution is to create a list of properties where dashed
idents can be used. However, they can _also_ use CSS custom properties
that point to another dashed ident.
A workaround that some people used is adding a `_` in front of the
variable: `mt-[_--my-thing]` this way we don't automatically inject the
`var(…)` around it. This is a workaround and gross.
While the idea of automatic var injection is neat, this causes more
trouble than it's worth. Adding `var(…)` explicitly is better.
A side effect of this is that we can simplify the `candidate` data
structure because we don't need to track `dashedIdent` separately
anymore.
* update tests by adding `var(…)` explicitly
* update changelog
* skip initial character we just saw
Thanks Richard for noticing!
Co-authored-by: Richard van Velzen <richard@frank.nl>
* prevent calling `charCodeAt()` if we already computed the value
* update changelog
---------
Co-authored-by: Richard van Velzen <richard@frank.nl>
* implement custom `compare` for sorting purposes
This `compare` function compares two strings. However, once a number is
reached the numbers are compared as actual numbers instead of the string
representation.
E.g.:
```
p-1
p-2
p-10
p-20
```
Will be sorted as expected in this order, instead of
```
p-1
p-10
p-2
p-20
```
---
This should also make suggestions in the vscode extension more logical.
* update tests to reflect order changes
* update changelog
* reset `i` correctly
This makes the code more correct _and_ improves performance because the
`Number(…)` will now always deal with numbers.
On the tailwindcss.com codebase, sorting now goes from `~3.29ms` to
`~3.10ms`
* drop unreachable code
In this branch, it's guaranteed that numbers are _different_ which means
that they are never going to be the same thus unreachable code.
When we compare two strings such as:
```
foo-123-bar
foo-123-baz
```
Then all characters until the last character is the same character in
both positions. This means that "numbers" that are the same in the same
position will be compared as strings instead of numbers. But that is
fine because they are the same anyway.
* add fallback in case numbers are the same but strings are not
This can happen if we are sorting `0123` and `123`. The `Number`
representation will be equal, but the string is not.
Will rarely or even never happen. But if it does, this makes it
deterministic.
* re-word comment
* add more test cases with numbers in different spots with various lengths
* Update CHANGELOG.md
* cleanup, simplify which variables we increment
This also gets rid of some explanation that can now be omitted entirely.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
* use shared tokens
* use token constants in candidate parser
* use token constants in `isColor` function
* add block now that `return null` goes to a new line
* Switch breakpoints to rem #8378
* Fix broken test
* Update snapshots
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>