Inspecting Class Conflicts0:00
In the previous lesson, we've taken a look at how to merge classes with the CLSX library, but it seemed like some classes were not taking effect. So let's take a closer look to understand what's happening. I'm going to fire up the dev tools and I'm going to inspect the second box here. And as a reminder, the second box is this one here where we were toggling the rounded-full, bg-yellow classes with the bg-blue based on the button toggle state.
where we were toggling the rounded-full bg-yellow-300 classes with the bg-blue-300 based on the button toggle state. So if the toggle is on, we add the rounded-full and bg-yellow-300 classes and if it's off we add bg-blue-300. Now these get composed with the base classes of the box component, which contain rounded-lg and bg-white. Okay, so the toggle is off here. So I'm expecting this box to have a bg of blue-300 and if we look at the dev tools,
So I'm expecting this box to have a bg-blue-300 and if we look at the dev tools, the bg-blue-300 class is here, but so is the bg-white. And if I scroll down in the stars here and find the background color classes, you can see that bg-blue-300 is being overridden by bg-white. Both classes are targeting the background color property and are basically competing with each other. Okay, so looks like bg-white is stronger than bg-blue-300.
and are basically competing with each other. Okay, so looks like bg-white is stronger than bg-blue-300. Now let's try to toggle the button on and oh, now it's yellow. Same deal. We have the bg-white class and the bg-yellow-300 class. But this time it looks like bg-yellow-300 is stronger than bg-white and therefore wins the specificity battle. Hmm, interesting that bg-yellow wins over bg-white, but bg-blue does not.
Hmm, interesting that BG yellow wins over BG white, but BG blue does not. Let's try with another color. Okay, instead of yellow, when the toggle is on, I will try red. So here I'll replace yellow with red 300 and oh no, it looks like now whenever the toggle is on or off, the box will remain white because both the blue 300 and the red 300 colors are both being overridden by the BG white color is.
and the red-300 colors are both being overridden by the BG white color. Is that a bug in Tailwind CSS or maybe a bug in clsx? Because to me it sure feels like whatever we pass to the class name attribute when we consume the box components. So here rounded-full BG red-300 or BG blue-300 should override and win over the base classes that are defined here. After all, we blend our className after these base classes.
CSS Source Order Rules2:25
and win over the base classes that are defined here. After all, we blend our class name after these base classes. So shouldn't they win every time? Well, no, this is not how CSS works. Even if the class name that we want to win is defined after the competing class name in the class attribute in the HTML. So for example, here we have bg-white first, but then we want bg-red to win and it's defined after this still doesn't matter.
but then we want BGRed to win and it's defined after this still doesn't matter. What matters is the source order in the CSS outputs. So if we go take a look at the CSS outputs, uh, here I'm in development. So this is going to be in a style tag in a document head and I believe it's this one. Let's get a bit of room here and if I scroll down, I will eventually find the utility classes for the background colors.
and if I scroll down, I will eventually find the utility classes for the background colors. Here we go. And here is the answer. So you can see bg-white is defined here, but now bg-red-300 and bg-blue-300 are defined before in the CSS output while bg-yellow-300 is defined right after bg-white, which is the reason why bg-yellow-300 was winning over bg-white. Now this output order might seem random, but if you take a closer look we can see bg-background-blue.
Now this output order might seem random, but if you take a closer look we can see BG background blue with B destructive P-R-S-S-W-Y. Yep, alphabetical order because the source order plays a key role in the cascades. The C in CSS tailwind, try as much as possible to be clever in what order its outputs the CSS uh, declarations. But whenever it comes to things like colors, it's kinda hard.
what order its outputs the CSS uh, declarations. But whenever it comes to things like colors, it's kinda hard to decide what colors should have more importance than another. So it's just resolved to outputting them in the alphabetical order of the color names. Let's take a look at another quick example. So we talked about the colors, but remember we also have the rounded-lg base class.
So we talked about the colors, but remember we also have the rounded-lg base class and then we try to apply rounded-full when the toggle is on. And here, same deal rounded-full is applied, but it's getting overridden by the rounded-lg, which happens to come later in the source order since L from LG comes after F from full in the alphabet. But now I bet that if I was using a rounded class that had a modifier that starts with the letter that comes after L in the alphabet, like rounded.
that had a modifier that starts with the letter that comes after L in the alphabet, like rounded. Yep, let's go with xl. There you go. It's kinda hard to tell, but the roundedness of this box is more rounds than this one. And if you scroll down here, we will find out that rounded xl has overridden the rounded lg and we can verify this in the source order where rounded xl comes
and we can verify this in the source order where roundedExcel comes after roundedLg, which is the base class. I don't know about you, but when I pass classes to my box component, I'm not trying to let the alphabetical order decide the fate of which class is going to win. What I want every single time is for the classes that are passed to the box component here to win and override the base classes.
Introducing Tailwind Merge5:13
that are passed to the box component here to win and override the base classes. I want a system that takes these custom classes that are passed to the component and make these classes win every time. And this is exactly what Tailwind Merge is set out to do. All right, so Tailwind Merge is the second library that is used in that CN function that we've been studying. So once again, it's already part of our dependencies. And so in our components definition down here, let's try
So once again, it's already part of our dependencies. And so in our components definition down here, let's try to replace CLSX with TW Merge that I will import from Tailwind Merge. And what Tailwind Merge will do instead of simply concatenating these classes with these classes, it's going to evaluate the conflicts between these classes and it's going to make sure that every single time the class is passed last, so in our case here in the class name are always going
that every single time the class is passed last, so in our case here in the class name are always going to win over the ones passed first. So let me save the file. And now remember that we pass rounded-lg and bg-white first, but then in the class name attribute here, we will have rounded-xl and bg-red all blue based on the toggle state. And you know what, I'll go back to the initial rounded-full and bg-yellow-300 that we had originally.
And you know what, I'll go back to the initial rounded-full and bg-yellow-300 that we had originally and let's go take a look. Aha, our toggle is on and this time we have the rounded-full border-radius applied and if I toggle it off, the box is blue. And if you pay attention to the classes passed to that box, you'll see that we do not find any conflicting classes. Tailwind Merge has identified the conflicting classes and removed them from the class attributes.
Tailwind Merge has identified the conflicting classes and removed them from a class attributes. So we only have rounded-lg and bg-blue-300 here. And when I toggle the button, take a look at the classes, the classes have been swept over. We only have rounded-full and bg-yellow-300 and that's exactly what we were hoping for. Whatever classes we pass to the box component are going to be applied no matter what the base styles say. And this is super powerful.
to be applied no matter what the base styles say. And this is super powerful. I encourage you to take a look at the Tailwind Merge GitHub repo. It has awesome documentation. Uh, it has an awesome intro video by this awesome guy right there. The features page in particular is a really good one if you want an overview of what is capable to do. But the essential behavior is
want an overview of what is capable to do. But the essential behavior is that the last conflicting class will always win. You can see that it works with modifiers and all the things in Tailwind, but one thing it doesn't do is handle complex object structures like CLSX is capable of. And this is exactly why the CN function in SHA CN UI is using both CLSX and Tailwind Merge. And basically it wraps CLSX in Tailwind Merge
using both clsx and Tailwind Merge. And basically it wraps clsx in Tailwind Merge and basically takes the best of both worlds and give you this super nifty utility function that you can use anywhere that will take a lot of complex different sets of classes and then intelligently resolve the conflicts and output exactly what you need. Alright, now that you know exactly how the cn function works, let's take a look at
Using cn in Components8:05
Alright, now that you know exactly how the CN function works, let's take a look at how it's being used in chat CN UI components. So we're going to add the input component here. Alright, so it's generated the component file and you can see that it's importing the CN function from the uts and then the input component can receive a class name attributes to pass custom classes. And then we'll have base classes here using the CN function. All of these here. And then there will be merge.
And then we'll have base classes here using the cn function. All of these here. And then there will be merge and conflict resolve with Tailwind, merge with the class names that are passed to the component. And that's it. That's the input component. So if in my welcome page I replace all of this with an input that will import from our UI components and let's remove the useState here. And maybe let's add a placeholder that says start typing da da.
And maybe let's add a placeholder that says start typing da da. Um, we'll have these default components. It doesn't look as great as it should, I guess because of the page background. So let's remove the background color here. Alright, so here's the default style of the input and the default input component has classes like width-full, but we can easily override this by passing with let's say 80.
but we can easily override this by passing 80. Here we go. And thanks to the CN function and tailwind-merge in particular, there will be no trace of width-full in the class set. It's a little bit hard to find, there's lots of classes, but believe me, there is no width-full because tailwind-merge has found w-80, which comes after and has made this one win and removed the full class. So the ability to pass custom classes to any SHATI
Overriding Component Styles9:34
and has made this one win and removed the ful class. So the ability to pass custom classes to any SHATI and UI component through a class name attribute, which will be then merged with the base classes is a common pattern to all components. And here it's just merging base classes with the classes you pass. But if we look at the other components that we already have generated here, and let me quickly remove the box components
that we already have generated here, and let me quickly remove the box components that we are not using anymore here. So TypeScript is happy, there we go. So if I look at the button once again, we are going to import CN from the uts and then here it's a bit more complex but we have a bunch of classes defined. And then in the button definition, all we do is use the CN function to merge
And then in the button definition, all we do is use the CN function to merge and resolve conflict of this button variants including the class name past last, which means that I can pass any class name to the bottom component. And no matter what happens here, my classes, my custom classes will always win. Alright? At this point I'm very confident that whenever you see a CN function in the world,
Previewing Button Variants10:37
Alright? At this point I'm very confident that whenever you see a CN function in the world, in the ShadCN UI component, you know and understand exactly what it does. And speaking of that complicated looking thing that we were looking at just then, these button variants, this is going to be the topic of our next chat, CNUI, exploration.
