TailwindCSS is great for building reusable components of your design system. The component itself can be written in ReactJs or VueJs and we'll get the same benefit.
But How?
Today, I'll just talk about one reason and that is Encapsulation. When we write a component we want to allow some customizations but on the other hand, we also want to prevent full access to the internals of a component. That is to say we want to expose a public API and only allow customisation from that public API.
First I'll shed some light on why traditional styling doesn't provide Encapsulation.
ClassNames is a public API.
In React, a components public API is made with props and in Vue we have props + slots. But we are forgetting one thing. If a component uses CSS classes for styling then we are unknowingly providing another public API. Because all CSS classes are global, any consumer of the component can override the internal styles of a component.
Take a look at this example
function Button({ icon, size='normal', children, ...props }) {
return (
<button {...props} className={`btn btn-${size}`}>
<span className="btn-icon">
<Icon icon={icon} />
</span>
{children}
</button>
);
}
It's a simple Button component which can render text and an icon. There is a size
prop as well with which user can render a normal button or a large buttton. It makes sense because we don't want user to render a button of any size. We want the size to be constrained. Also this way we can scale the icon according to size of button. If we give user the full control then they might forget increasing the size of icon while making a large button.
So we have two requirements-
- User should only render a normal or large size button.
- Everything inside button like font and icon should scale along with button size.
But is the current implementation really following the two requirements?
The answer is No.
Because user knows about the two classNames btn
& btn-icon
they can customise both the elements in any way they want. Assume user writes this CSS-
.btn {
font-size: 100px;
padding: 10px;
}
.btn-icon {
font-size: 20px;
}
All of the hard work you did to keep the scale of icon and button in sync is now wasted. User has unregulated control, it's chaos.
To put it in short there are two problems with using classNames like this for components of a design system-
- User has full control of the styling and you can't add constraints.
- If you change the internal structure, user's applications can break.
I'm a lot more scared about the second problem. Because now the user relies on the internal structure of the component you can't modify the internals carefree. You cannot change the btn-icon
class to something else. And that's just one thing. Imagine all the classes a component is made of. Once you publish the component you have tied yourself in a corner, you cannot modify anything you can just add new things. And when you just add new things your API surface becomes larger and more complex.
This xkcd comic is exactly what I'm talking about
Tailwind to the rescue
TailwindCSS provides a simple solution for this. Just don't use classes this way. If you don't have a catch-all btn
class then users won't be able to override it.
A Button
styled with TailwindCSS would look like this-
function Button({ icon, size='normal', children, ...props }) {
return (
<button {...props} className={`bg-green text-${size}`}>
<span className={`bg-gray-600 text-${size}`}>
<Icon icon={icon} />
</span>
{children}
</button>
);
}
What is important to notice is that the entire component is made of utility classes. These classes just do one small thing and each class might appear in multiple components. A user would not override bg-gray-600
because that would break all those components which use bg-gray-600
class. Earlier the user knew that btn-icon
would just affect the icon inside Button component so they modified it carefree but now they can't.
Now you have the control on what things you want the user to give control of. User can't even change the color of button if you don't provide a prop for it.
Hope this article helped you understand one benefit of TailwindCSS. I'd be writing more about Tailwind and design systems in general so if you're interested in that give me a follow here or on Twitter.
Top comments (2)
Suppose if a user overrides a utility class intensionally to reflect it across the entire product(say padding-10 to be 12px padding) he is building, it would break the design system guidelines and also breaks the meaning of the utility class, how do you think we can avoid that?
If say a team does that when just starting to use design system then they'll instead ask the design system to make the change themselves. Design System exist to serve the product.
If say a team makes the change after some time of adapting the DS then they'll potentially change a lot of their product's design. So I think their would be a low probability of this happening.
The design system team has to teach the product teams not to do this at any cost. It's basically a cardinal sin.
The design system team needs to actively monitor the product to see how they're using the design system. If the usage is not recommended then go back to the product team, discuss with them and improve communication channels.