mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
One of the breaking changes of v4 is the [inversion of variant order application](https://github.com/tailwindlabs/tailwindcss/pull/13478). In v3, variants are applied "inside-out". For example a candidate like `*:first:underline` would produce the following CSS in v3: ```css .\*\:first\:underline:first-child > * { text-decoration-line: underline; } ``` To get the same behavior in v4, you would need to invert the candidate order to `first:*:underline`. This would generate the following CSS in v4: ```css :where(.first\:\*\:underline:first-child > *) { text-decoration-line: underline; } ``` ## The Migration The most naive approach would be to invert the variants for every candidate with at least two variants. This, however, runs into one issue and some unexpected inconsistencies. I have identified the following areas: 1. Some pseudo class variants _must appear at the end of the selector_. v3 was patching over this by doing some manual reordering in for these variants. For example, in v3, both of these variants create the same output CSS: `hover:before:underline` and `before:hover:underline`. In v4 we simplified this system though and no longer generate the same output in both cases. Instead, you'd always want to write `hover:before:underline`, ensuring that these variants will appear at the end. For an exact list of which variants these affect, take a look [at this diff](https://github.com/tailwindlabs/tailwindcss/pull/13478/files#diff-7779a0eebf6b980dd3abd63b39729b3023cf9a31c91594f5a25ea020b066e1c0L228-L246). 2. The `dark` variant and other at-rule variants are usually written before other variants. This is more of a recommendation to make it easier to read candidates rather than a difference in behavior as `@media` queries are hoisted by the engine. For this reason, both of these variants are _correct_ yet in real applications we prefer the first one: `lg:hover:underline`, `hover:lg:underline`. To avoid shuffling these rules across all candidates during the migration, we bucket `dark` and other at-rule variants into a special bucket that will not have their order changed (since people wrote stacks like `sm:max-lg:` before and we want to keep them as-is) and appear before all other variants. 3. For some variant stacks, the order does not matter. E.g.: `focus:hover:underline` and `hover:focus:underline` will be the same. We don't want to needlessly shuffle their order if we have to. With these considerations, the migration now works as follows: - If there is less then two variants, we do not need to migrate the candidate - If _every_ variant in the stack is an order-independent variant, we do not need to migrate the candidate - _Note that this is currently hardcoded to only support `&:hover` and `&:focus`._ - Otherwise, we loop over the candidates and put them into three buckets: - `mediaVariants` hold variants that only contribute `@media` rules _and_ the `dark` variant. - `pseudoElementVariants` hold variants that _must appear at the end of the selector_. This is based on the allow list from v3/early v4. - `regularVariants` contains the rest. - We now compute if any of the variants inside `regularVariants` is order dependent. - With this list of variants, we now construct the new order of variants as: ```ts [ ...atRuleVariants, ...(anyRegularVariantOrderDependent ? regularVariants.reverse() : regularVariants), ...pseudoElementVariants, ] ``` --------- Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
A utility-first CSS framework for rapidly building custom user interfaces.
Documentation
For full documentation, visit tailwindcss.com.
Community
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
Discuss Tailwind CSS on GitHub
For chatting with others using the framework:
Join the Tailwind CSS Discord Server
Contributing
If you're interested in contributing to Tailwind CSS, please read our contributing docs before submitting a pull request.