tailwindcss
tailwindcss copied to clipboard
[v4] `addComponents` is adding styles to `@layer utilities` instead of `@layer components`
What version of Tailwind CSS are you using?
v4.0.0-alpha.34
What build tool (or framework if it abstracts the build tool) are you using?
@tailwindcss/cli
Reproduction URL
https://github.com/saadeghi/tw4-component-layer-issue
Describe your issue
These are the layers in output CSS file:
@layer theme, base, components, utilities;
Expectation
It's expected for addComponents to add styles to @layer components
Current behavior
Currently addComponents adds styles to @layer utilities, similar to addUtilities
Plugin example:
https://github.com/saadeghi/tw4-component-layer-issue/blob/master/myplugin.js
Generated style:
https://github.com/saadeghi/tw4-component-layer-issue/blob/9b7a944690a35d55c7406756e30cc98c7a239623/output.css#L516
Hey!
In v4 this is done on purpose. The biggest benefit of the split in v3 is that you can always override a component with more specific utilities. However, in v4 we improved sorting of all the utilities, which means that this rule should still apply but it reduces the amount of concepts in the system.
Can you share some of the issues you are seeing because of this?
Thanks for the quick answer.
Sure.
The issue is order of styles which is important because order is important in CSS.
I updated the example: https://github.com/saadeghi/tw4-component-layer-issue
For example here I have 2 plugins:
https://github.com/saadeghi/tw4-component-layer-issue/blob/master/input.css
addComponents adds both of them to @layer utilities:
https://github.com/saadeghi/tw4-component-layer-issue/blob/064182b7d683bde9c9bfb9c618a334ed33136202/output.css#L512
But I have no control over the order:
@layer utilities {
.bigcard {
background: green;
padding: 2rem;
}
.p-10 {
padding: calc(var(--spacing) * 10);
}
.card {
background: yellow;
}
}
For example why one plugin came before p-10 and the other came after p-10?
How are they being sorted
Changing the order in input.css doesn't change anything.
Scenario
What can I do to make sure .bigcard comes after .card? because I want it to modify the default .card style so it must come after .bigcard?
I tried using addUtilities for .bigcard but it works exactly like addComponents.
Workaround
I know I can use:
- low specificity styles like
:where() - CSS variables to modify styles in modifier class names instead of CSS rules
But these solutions increases the CSS size and complexity. So I hope there's a way I can control the styles for component (.card) always come before the modifier styles (.bigcard) and Tailwind utility classes.
Expected order:
/* component */
.card {
background: yellow;
}
/* modifiers */
.bigcard {
background: green;
padding: 2rem;
}
/* utilities */
.p-10 {
padding: calc(var(--spacing) * 10);
}
So the style for this HTML would be predictable
<div class="card">default</div>
<div class="card bigcard">bigcard style overrides the default</div>
<div class="card bg-red-500">bg-red-500 overrides the default</div>
Yep I see, the order inside of your CSS is indeed very important. In v4 how it works is that we sort all the utilities (and components) based on the used properties in the CSS and the amount of properties you used.
The idea is that a component with multiple properties (which is typically the case) will be sorted before the ones with fewer properties. In your case .card has a single property and .bigcard has 2, so you can override the background color of .bigcard with .card.
The order difference between .card and .p-10 is based on the used properties. Since the properties used in those classes don't have any overlap, the order between those classes doesn't matter (note: the output is still deterministic and won't appear in random spots during rebuilds).
It also looks like the .card and .bigcard examples are made up to simplify the reproduction (which I appreciate!). But since sorting is based on the amount of properties and which properties were used, a more real example will help here.
Can you make a reproduction with a real example just to verify that you are still running into these issues?
sorting is based on the amount of properties and which properties were used
I see! Thanks for the info.
I have a lot of modifier classes, with less or more properties than the component class, and some override others but the problem was I didn't know why they are ordered this way and I thought putting the components in the @layer components would give me a predictable order.
👍 I think you can close this issue if there's no plan to use @layer components. I will check and modify my components to make sure the modifiers have less styles than the components.
The idea is that a component with multiple properties (which is typically the case) will be sorted before the ones with fewer properties
sorting is based on the amount of properties and which properties were used
Can you confirm If I understood the logic correctly:
If a class name has a property that overlaps with properties from another class name, the one with more properties comes first?
@saadeghi I ran into similar problems while migrating to v4 and decided to use this pattern:
@layer utilities.components.base, utilities.components.modifier;
@utility btn {
@layer components.base {
/* base css for btn */
}
}
@utility btn--outlined {
@layer components.modifier {
/* specific css for outlined */
}
}
So for example, for this markup...
<button class="btn btn--outlined bg-blue-500">...</button>
...specificity is correctly observed: btn < btn--outlined < bg-blue-500, regardless of the ordering in output.
Verbose? Yes, but works well for my use cases. Similarly in JS syntax:
api.addUtilities({
'.btn': {
'@layer components.base': {
// ...
},
},
})
Hope that's helpful.
Related to this, I would add a new function that adds styles outside the TW layer structure:
https://github.com/tailwindlabs/tailwindcss/discussions/15074
Something like:
addCSS()
Similar issues, when using the tailwind-forms plugin, they are now added to the utilities section, which means I also needed to move my form additions to the utilities layer now instead of the components layer that it was in in v3.
@layer utilities {
/* Forms */
.form-input,
.form-textarea,
.form-select,
.form-multiselect,
.form-checkbox,
.form-radio{
@apply block w-full rounded border-0 py-1.5 cursor-pointer
text-gray-900 shadow-xs ring ring-inset ring-gray-200
placeholder:text-gray-400 focus:ring-2 focus:ring-inset
focus:ring-indigo-600 sm:text-sm sm:leading-6;
}
The issue here is now if I want to do a rounded-r-none if I want to make no rounded corners on the right side to butt up against a button for example, I need to make some (but not all, seemingly random based on order) of the utils important like !rounded-r-none.
It feels like the form plugin would want to be added in the components layer, since they apply a lot of styles that you want to control and be able to override on top of inline.
Here is a working example of the issue: https://play.tailwindcss.com/kuXFgLDpPH
Does anyone know a workaround for this issue?
Here’s my specific use case:
In Tailwind v3, I used a plugin to add custom classes to the components layer like this:
const plugin = require("tailwindcss/plugin");
module.exports = plugin(function ({ addComponents }) {
addComponents({
".card": {
background: "green",
},
});
});
This allowed me to use the .card class in markup and override its background color with a utility class:
function App() {
return <div className="card bg-red-100">card content</div>;
}
The bg-red-100 utility would take precedence over the .card background.
However, after upgrading to Tailwind v4, addComponents now places classes in the utilities layer, making it impossible to override them with utilities like bg-red-100.
Is there any way to force addComponents to add classes to the components layer in v4?
@goofylito you can have a look at my comment above https://github.com/tailwindlabs/tailwindcss/issues/15045#issuecomment-2490484318 for a possible workaround at the moment.
I'd say having the ability to add directly to the components layer, however, is indeed much more convenient.
Hello @RobinMalfait,
I'm experiencing the same issue with CSS generation for component classes and overriding with Tailwind utilities.
Will this be addressed, or will it remain as is?
I’m also running into issues because of this change. It was much easier to manage custom styles when addComponents placed them in @layer components. Now, having to put everything in @layer utilities creates conflicts with Tailwind’s utility classes.
I believe reducing the number of concepts is beneficial, which is the rationale for this change. However, IMO, there should be another way to achieve what @layer components was intended to do. It existed for a reason.
In my project, I ended up creating an or: variant, which is basically what @adamwathan demonstrated here: https://x.com/adamwathan/status/1818041514863608141
@custom-variant or {
@layer components {
@slot;
}
}
Although addComponents() is now an alias of addUtilities(), the components layer is still declared by Tailwind, so I leveraged it instead of creating a new layer.
This way, if the component has or:text-red-500 but receives text-blue-500 from the props, the latter wins.
Fun (actually sad) fact: I used or: because it takes less space than default:. My initial attempt was ?:, to resemble the nullish coalescing operator. I managed to patch IS_VALID_VARIANT_NAME with a Shell script (since I couldn't use patch-package because Tailwind is sadly distributed in a minified format). However, I later discovered that Oxide, which is a binary and can't be patched, doesn't even consider tokens starting with ? as valid candidates.
Love this @gustavopch. I've wasted hours trying to override select parts of tailwindcss-typography without resorting to !important rules everywhere, but given up. This solves it.
Problem
- You want the tailwind-typography goodness in your site so you import the plugin
- The base styles aren't quite right so you add a few global override rules
- You encounter a specific element where your override styles aren't suitable (but they're perfect everywhere else)
- 💥 Boom. To override your override you have to resort to using
!importantclasses 😭
Solution
Create @gustavopch's cool variant which injects styles into their own (child) layer.
@custom-variant typo { /* <== variant prefix */
@layer typography { /* <== child layer name */
@slot;
}
}
Use that as a prefix for the tailwind/typography .prose classes wherever you use them (e.g. on a blog post template).
<main class="typo:prose typo:prose-slate typo:dark:prose-invert typo:lg:prose-lg">
...
</main>
Tailwind will compile the typography styles into the child layer utilities.typography, which you can then use to target overrides.
@layer utilities.typography {
.typo\:prose {
line-height: var(--leading-normal);
max-width: none;
h1, h2, h3, h4 {
font-weight: 500;
margin-bottom: 0;
}
...
}
}
Because the utilities.typography child layer has lower specificity than its parent utilities layer, the normal tailwind utility classes take precedence and no ! important shenanigans is needed. \o/
this is such an annoying thing to deal with, it makes it hard to use DaisyUI or similar Tailwind frameworks since all defined components and utilities end up in the same layer and there's no way to alter framework defaults, like adjusting paddings.
@gavar same issue here. at our company we can't upgrade to v4 because of this layer/specifity problem. Overriding seems impossible with @tailwindcss/forms and @tailwindcss/typography plugins enabled.
This is also causing me problems with @tailwindcss/typography as @markchitty described. I had opened an issue in the typography repo for this yesterday: https://github.com/tailwindlabs/tailwindcss-typography/issues/397
Adding my voice here Adding components as utilities messes the specificity, then it's harder to override the component and you end up with a lot of !important, components should be added to the components layer