-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: 3.2.6 should be a major release as it includes breaking changes regarding overriding class names #10603
Comments
Hey @jdpowell1! So to really explain this I need to explain how Tailwind sorts CSS rules generally, which is a lot of information so bear with me — will try to make it as digestible as possible. First, every class in Tailwind comes from some sort of internal plugin, and each plugin usually maps to a CSS plugin. For example, there's a We maintain a list of these plugins internally in a particular order, and all of the generated CSS follows this order, so that all of the classes handled by a single plugin are grouped together, and those groups are sorted according to the plugin order. So given this HTML for example: <div class="flex bg-red-500 text-sm">
<div class="inline-block bg-blue-500 text-xl">
<!-- ... -->
</div>
</div> ...the following CSS is generated: /* `display` utilities */
.inline-block {
display: inline-block;
}
.flex {
display: flex;
}
/* `background-color` utilities */
.bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
/* `font-size` utilities */
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
} Now in addition to this sorting logic, we also do some sorting within certain plugins to make sure that more specific utilities from a particular property category override more general utilities. An example of this is that That ensures that things like this work: <div class="p-5 pr-4"> Beyond that level though, in Tailwind CSS v3.2.4 and earlier, the generated CSS is sorted based on the order Tailwind "sees" the class names in your templates. That means that the sort order for any two utilities that were part of the same "category" was non-deterministic. So for example, given this HTML: <div class="bg-black">
<div class="bg-white">
<!-- ... -->
</div>
</div> ...Tailwind would scan it for classes, find .bg-black {
background-color: #000;
}
.bg-white {
background-color: #fff;
} But given this HTML: <div class="bg-white">
<div class="bg-black">
<!-- ... -->
</div>
</div> ...the generated classes would be in the opposite order: .bg-white {
background-color: #fff;
}
.bg-black {
background-color: #000;
} ...and because a class name will only ever appear in the final CSS once but can appear in templates many times, this ordering is based just on the first time Tailwind sees each class. This means that any time you change the HTML in an existing file, or add a new file that contains utility classes, or even compile your CSS on a different computer that happens to give Tailwind the list of files from the filesystem in a different order that the resulting CSS could have slight differences in the order of "conflicting" classes. Historically we have considered this a non-issue and avoided making this deterministic to maximize performance, because we have always considered it user error to add two competing classes to the same element: <div class="bg-black bg-white">
Should I be black or white?
</div> Our IntelliSense extension marks this as a lint warning for example: So even though the behavior has changed for v3.2.5+, adding two conflicting classes to the same element has always been an authoring error in our minds. Okay so all of that explained, I understand that even with an explanation it still feels like a frustrating breaking change. The reality is though any project that was doing this was already at risk of breaking any time you changed something in another file. You gave this example:
This isn't actually true — it might work that way in your project right now, but if you ever add Here are some demos that shows that, running v3.2.4: Demo 1, where You should be able to replicate this in your own project by just adding Also, if overriding In CSS, the order of the classes in your CSS determines which rules are applied, not the order of the classes in your HTML. That means that in this example, both elements will be black: <style>
.bg-white {
background-color: #fff;
}
.bg-black {
background-color: #000;
}
</style>
<div class="bg-white bg-black">...</div>
<div class="bg-black bg-white">...</div> So again even though the output has changed a bit for v3.2.5, the behavior you were seeing in v3.2.4 and earlier was non-deterministic, and overrides that worked one day could break the following day if someone used one of those classes in another file in a way that caused Tailwind to ingest the class names in a different order. This is trivial to trigger, as demonstrated by the demos above. Now in v3.2.5 and up, the generated CSS order is always deterministic, but even so we still highly discourage adding conflicting classes to the same element and still consider it an authoring mistake. The generated CSS will at least always be the same going forward, so the precedence of classes won't surprisingly change just because you compiled the CSS on a different computer, but because the order of the class names in the I hope that helps — again the key thing to understand here is that if you were doing this sort of overriding in v3.2.4 and earlier, that behavior could break at any time just by adding a class in a different file. So it might feel like this is a breaking change but really the project was already "broken" because there was no guarantee that the next time you compiled your CSS that you were going to get the same output that you got the previous time. Put another way, So even if we reverted the change that was made here, your project would still be at risk of breaking any time you made a change to it. Sorry for the long-winded response but hopefully it includes all of the detail necessary to really understand what's happening here! |
Hi @adamwathan, thanks for the very detailed response, it's really helped me understand the problem you're facing deep at the CSS level. Is this intricacy explained in the documentation? I may have glossed over it! I'm glad I've found tailwind-merge, which should help us to achieve the same result in a semantically correct, deterministic way! Thanks for all you and the Tailwind team have done, you've really transformed our workflow around styling our components. |
#10552 and #10543 are examples of this.
This comes down to the fact that although to some it's considered a bug, many people (myself included) use it as a feature and override the tailwind CSS classes, expecting the CSS class to be overwritten with the latest value specified. Since this functionality has been changed (there has been no indication of the change other than these issues) between these versions many of our components are now rendering in a completely different layout between the two versions.
For example, I may have a
Card
component with a default ofp-4
but I may want to override that top-2
in some circumstances. On version3.2.4
I was able to just use something like classnames (https://www.npmjs.com/package/classnames) to merge the strings together on the base component and the later value would take precedence. In order to do that now on3.2.6
, I have to use something liketw-merge
(https://www.npmjs.com/package/tailwind-merge) to make this happen, which removes the previously defined tailwind values.While it seems on the surface like this is just a fix to a minor bug, it can have a big impact on web apps that have many abstracted components with overriding tailwind classes, which is why I believe this change should result in a major release rather than a patch.
The text was updated successfully, but these errors were encountered: