There are two extremes in building our modern UI with a consistent application of a design system:
Rely on pre-themed UI component libraries and override styles as needed**
Avoid pre-themed UI components by doing everything from scratch**
If you’re not careful, you can end up with a weird blend of styles from a design system and the pre-themed UI component library.
The lines between the design system and the pre-themed UI component library get blurred by devs (and potentially even designers).
You have to pour a large amount of resources into a building a design system and custom UI components.
This is fine for larger companies, but it’s inefficient (if not impossible) for everyone else.
This begs the question: What assets do we really need to improve productivity without compromising the “health” (that is, the consistent application) of a design system?
On one hand, we need an efficient way to “apply” the design specifications of a design system to a UI.
On the other hand, we also desire an efficient way to encapsulate the experience/functionality/behavior of UI elements.
The issue with a pre-themed UI component library is that it tightly couples applying styles and applying functionality which are conceptually distinct.
This is problematic because:
1) Not all UI elements encapsulate functionality. So, a library can’t apply all the styles (a component can’t be the only means of applying styles)
2) You are forced into two things (encapsulated styles and functionality) when you may just want one
Granted, the appeal of a pre-themed UI library is its efficiency as it combines two things into one.
On the other hand, doing everything from scratch is appealing because you can separate styles and functionality (and therefore have complete control over those separate things).
There is a middle ground solution that offers 1) a separation of the concerns of applying styles and encapsulating functionality and 2) the ability to speed up development by sharing the functionality without pre-application of styles (protecting the health of the design system).
What does this middle ground solution look like?
Headless UI by @tailwindlabs separates applying styles from encapsulating functionality via components.
Use the library to bypass rewriting basic UI component logic, but apply styles as you please (without having to override).
And what’s the best way to apply styles from a design system? A design tokens pipeline which I’ve a written about here:
So what’s the ideal stack?
A design tokens pipeline + Headless UI (or, a custom equivalent).
The design tokens pipeline represents all the design specifications in code via assets (like SASS variables). These assets can then be applied to the components and other web elements.
Specifically, I would either use a plug-in to extract design specifications from a design file (as JSON) or write them out manually with designer and developer input.
Then, I would use a build tool that translates the JSON into assets (something like Style Dictionary).
Then, use a CI/CD pipeline to “deliver” the assets to the repos of all consumers (the web applications and the UI component library, Headless UI or a clone/fork).
If you want expedite this part, use something like @specifyapp before jumping the gun to something like Material UI.
Most specifically, I would have the design tokens pipeline create a Tailwind configuration so that I could use @tailwindcss and Headless UI (or, a custom equivalent). I’ve written how to do that here:
That’s it. Time to go back to sleep.
Please consider sharing the post if you found it helpful.