-
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
Can we talk about the class sort order? [Prettier Plugin] #12821
Comments
Hey! Can you you post a few before and after examples comparing how things are sorted now vs. how you think they should be sorted? |
Hi Adam, sure. This is the title to a tab for Mac style "tabbed window" that I made. The tab is styled based on whether it's in the default state, the active state, or neither: <!-- now -->
<h4
class="not-prose flex h-8 w-1/2 place-content-center
border-r border-zinc-500
bg-gray-400
text-xs
text-gray-200
hover:!bg-gray-500 hover:!text-gray-300
group-target:!bg-gray-400 group-target:!text-gray-200
group-has-[:target]/tabs:bg-gray-600 group-has-[:target]/tabs:text-gray-400"
>
<!-- proposed -->
<h4
class="flex h-8 w-1/2 place-content-center
border-r border-zinc-500
bg-gray-400 hover:!bg-gray-500 hover:!bg-gray-500 group-target:!bg-gray-400 group-has-[:target]/tabs:bg-gray-600
text-xs
text-gray-200 hover:!text-gray-300 group-target:!text-gray-200 group-has-[:target]/tabs:text-gray-400
not-prose
"
> background goes with background, text color goes with text color. If a two classes override the same rule then the one with higher specificity is sorted after the lower.
Another example: <!-- now -->
<section
class=" tab-panel-content
max-h-96
overflow-auto
border-t border-zinc-600
group-has-[input.min-window:checked]/window:hidden
group-has-[input.max-window:checked]/window:max-h-[89dvh]
group-has-[input.max-window:checked]/window:max-w-[100vw]
prose-img:max-h-full
prose-img:max-w-full
md:group-has-[input.max-window:checked]/window:max-h-[82dvh]
md:group-has-[input.max-window:checked]/window:max-w-[82vw]
"
>
<!-- proposed -->
<section
class="tab-panel-content
group-has-[input.min-window:checked]/window:hidden
max-h-96
group-has-[input.max-window:checked]/window:max-h-[89dvh]
group-has-[input.max-window:checked]/window:max-w-[100vw]
md:group-has-[input.max-window:checked]/window:max-h-[82dvh]
md:group-has-[input.max-window:checked]/window:max-w-[82vw]
overflow-auto
border-t border-zinc-600
prose-img:max-h-full
prose-img:max-w-full
"
> The user defined class Final example: <!-- now -->
<div
class="fixed left-0 top-0 z-20 hidden
bg-black/90
peer-has-[input.max-window:checked]:block
peer-has-[input.max-window:checked]:h-screen
peer-has-[input.max-window:checked]:w-screen"
>
<!-- proposed -->
<div
class="fixed left-0 top-0 z-20 hidden
peer-has-[input.max-window:checked]:block
peer-has-[input.max-window:checked]:h-screen
peer-has-[input.max-window:checked]:w-screen
bg-black/90"
> All the stuff related to |
This comment was marked as spam.
This comment was marked as spam.
Appreciate the feedback here, going to close in favour of the existing discussion though since this isn’t a bug and is by design. To be totally honest making any changes here is very low on my priority list so it’s unlikely we dig into this or explore anything different here any time soon, especially since the current order doesn’t bother me at all. Still appreciate the feedback, just trying to be transparent about where I stand on it 👍🏻 |
Discussed in #12804
Originally posted by asimpletune January 22, 2024
Originally I was going to write this in the PR for biomejs, but then I figured it's actually a tailwind-wide question.
As I understand, the sort order is based on the order of how class are generated in the resulting CSS. I think this is a very good start, and handles most of what one wants. However with variants I don't think that is the case, and I'd like to explain why.
The best possible outcome of this discussion is I just realize that I'm wrong (extremely likely possibility) and that's 100% ok with me, even preferred. If I'm wrong, then we can all sleep at night knowing we already do things the best we know how. However, there is the possibility that we do not currently have the best way of sorting classes, in which case there is room for improvement. So let's hope I'm wrong, as that is certainly easier, but not shy away from the discussion.
Ok, here goes.
The rules behind Tailwind's class ordering
So the plugin's ordering works by the order of the generated CSS. This is mostly very good.
First, definitely, it is important to have classes that modify/override the same rules be sorted by specificity, so it's clear which takes precedence. The example from the docs of
p-4 pt-2
makes the case crystal clear, sincept-2
is more specific thanp-4
. The specific case should override the generic case.By that same logic, another way of expressing this rule is that "like goes with like", and ties go to whichever is more specific. After all, what could be more alike than two classes modifying the same rule?
Additionally, in the tailwind docs there is mention of a high level goal for the ordering as well:
This also makes sense, with the most impactful, layout rules coming first, decorative ones coming last.
There's a third goal, which I'm just mentioning for completeness, that user defined classes come first.
Therefore, according to the docs, we have 3 sorting rules:
On all accounts, I am 100% in agreement and I think it all makes perfect sense. However, there is another rule, regarding modifiers that basically conflicts with the first 3! It's a complete contradiction.
The issue comes with modifiers
The problem is this violates the rules above. It basically, logically, means 'order by impact, ties are broken by specificity, ... but modifiers are immune from all of this and are tacked on in the end'. Why?
Like I said, please don't mistake my meaning. This is better than nothing, but having the modifiers come at the end is honestly not useful.
I understand that modifiers need to be distinguished from regular utility classes, that is clear, but it doesn't need to happen with sorting! I know something is a modifier by virtue of it having a modifier in the name. So, from this view, we're duplicating the benefits that we already get, which on its own isn't harmful but it does beg the question:
Are there actively negative affects of distinguishing modifiers from utility classes by sorting?
The downside of having it this way is we undo all the good we accomplished with our 3 sorting rules from above, because now I must read all of my styles to understand what they do. By this logic, we gain a benefit that we already had, but it comes at the price of losing other benefits, so by this view it's a net loss. I will try to make this last point more clear, before explaining how this 'net loss' is totally avoidable.
Since modifiers come last, and a modifier can precede any utility class, that implies that I have to read all of my code to understand the layout aspects, because they're no longer sorted earlier. Further more, I can no longer rely on the "like matches like" rule, because there may be more "alike" that are hidden somewhere in whatever modifiers I have.
Even worse, this is for every modifier as well too! It's not like there's an invisible line where on one side is all the non-modifier stuff is sorted, and then on the other the modifier stuff is sorted. It's more like on the modifier side of the line, the 3 rules are respected but for each modifier! This means I MUST read every modifier to make sure nothing important was missed. In other words, I can't scan my code and rely upon the ordering to tell me what I need to know, because things aren't sorted anymore by impact or like with like.
However, this problem is only important if you have a lot of modifiers. Most people probably don't, which is why I want to give a real, non-contrived example of what I think is a reasonable thing to make, that at times has unnecessary friction because of this:
'Tabbed Window' example
I made a Mac-style tabbed window for displaying code that's all related in different tabs. I try to make everything I write work without requiring JS to function correctly, and I also optimize for browser reader modes. This means my markup is going to be a little bit more complicated than I'd like, and I have to therefore use modifiers a lot. The end result is really cool, I think, and I love that in reader mode they're just normal document sections, complete with their tab's title being the section title. Here are some screenshots:
Normal mode:
After clicking the green button:
After clicking the yellow button:
The end result is it basically works how you expect, but the logic is complicated.
The most important of that logic comes down to layout, as I want to expand the window if they press the expand button, or shrink if they want to minimize that example. This alone would be fine, because the layout isn't that complicated after all, however I also have to handle the styling of the different tabs.
When one tab is selected, I use a document fragment link (to that tab) in combination with some tailwind modifiers to make sure the expected thing happens with the tabs's styling. The default tab though has no document fragment link in the browser so it may not use any modifier at all. And things quickly become sort of a mess, as I'm sure you can understand. There's making sure the tabs background is lighter, its text is lighter, but the non-selected ones are respectively darker, and that when you hover over a tab it becomes medium levels of light.
Having a default that only applies to one tab, plus decoration that happens when one tab is selected, and separate decoration for everything if a tab is not selected, which the default tab must also adopt because it's not the default case any more is... very complicated.
Ok, so it's complicated, but my point is that the modifier sort order does not help. I have peers, I have groups, I have all sorts of stuff going on there. They all handle layout and decoration, but then those things also exist in the non-modifier area of the class list. I still think this is a cool thing to make, and I think it's not some extreme example of having crazy amounts of modifiers. The code still consists of totally readable, semantic HTML, and I barely had to write any CSS.
We already have the better way
I want to thank whoever read all this way, and I don't want to end this without highlighting what I think would be a better way to do this. Therefore, I think the first 3 rules actually are great, and modifiers should just be forced to respect them.
This actually makes a lot more sense to with how they work, since modifiers will already override competing rules of less specific stuff. I think the difficulty would be that it is just more complicated to implement, but that's a separate discussion. I'd like to know what other users and maintainers think.
Thanks for reading.
The text was updated successfully, but these errors were encountered: