How we found sanity in a subset of Tailwind CSS
Sep 13, 2022 · 6 min read
How we found sanity in a subset of Tailwind CSS
Since its inception, the idea of utility CSS has been met with both praise by some and disdain by others. Tailwind took utility CSS to the extreme, and so has been especially controversial.
Those who love it will say...
- "It is a relief not having to come up with unique class names all the time!"
- "It is a relief not having to switch context back and forth between the markup and css!"
- "It is a relief to have a limited set of property values to choose from!"
And those who do not love it will say...
- "It is a pain to keep long lists of classes in sync throughout!"
- "Such fine-grained classes actually inhibit consistency!"
- "Mixing style and content severely hurts legibility and maintainability of both!"
- "It's all different/new names to learn for already-standard properties and values!"
- "Taken to its conclusion, it's basically just inline styles!"
And then those who love it will say...
- "You can just use
@apply
and/or React, etc to abstract the long class lists!"- "Or just use multiple cursors in your editor when editing class lists!"
- "These names are shorter and more descriptive than the standard ones, and the tailwind docs are fantastic!"
- "The complexity has to go somewhere, and we're never going back to BEM!"
- "Inline styles are not inherently bad, that was just hipster fashion to rag on them!"
And then those who do not love it will say...
- "
@apply
to abstract a set of styles is literally what actual classes were meant for!"- "Using React / styled-components / twin.macro to solve this just takes us further away from standards and adds yet more custom interfaces and unnecessary abstractions to do what regular old HTML/CSS already support!"
And it will all go around in circles. Guess what though? Everyone is right! Tailwind is both wonderful and terrible. Fully BEM everywhere is crazy — it's too much. Recreating the cascade with bespoke programmatic abstractions is crazy too — you shouldn't need a component framework for something as fundamental as a button.
So then what is the answer?
We have found it, and the answer is to do it all in strategic moderation: Use a subset of tailwind just for spacing and layout and revel in simple things being simple. Use modular CSS for UI patterns and revel in the readability of your HTML and visual consistency of your UI. And then use custom scoped CSS where required, and revel in interesting things being only as hard as need be, without ruining everything else along the way.
Which subset of Tailwind to use?
We found that by adding a small number of utility classes, mostly to do with spacing and layout, we could get rid of the need for semantic class names in a sizeable number of instances. It is a relief now when we want to nudge an element down by a few pixels not to have to BEM and context switch and all of that. Once we had moved away from spacing and layout being specified in element-specific classes, we looked at what other properties being specified were responsible for the largest number of remaining single-property classes, and pulled a choice few of those into the subset as well. These were properties like pointer-events: none
and display: flex
. We wrapped this up into a project we call alwin.css, and we invite you to try it out!
1234567891011 <
div
class
=
"mt-24 mb-32"
>
<
header
>
<
h4
class
=
"mt-32 mb-8"
>Enter your name</
h4
>
<
p
class
=
"mt-0"
>First and last name for your account</
p
>
</
header
>
<
form
>
<
input
class
=
"mb-8"
>
<
button
class
=
"mr-8"
>Submit</
button
>
<
button
class
=
"ml-8"
>Cancel</
button
>
</
form
>
</
div
>
Modular CSS
We use a handful of CSS modules for styling UI elements consistently. Buttons, form inputs, avatar images, icons, hero sections, modals, etc. Patterns that are specified in our design style guide generally have corresponding modular CSS classes, in BEM.
1234567891011 <
div
class
=
"card modal mt-24 mb-32"
>
<
header
class
=
"modal__header"
>
<
h4
class
=
"mt-32 mb-8"
>Enter your name</
h4
>
<
p
class
=
"mt-0"
>First and last name for your account</
p
>
</
header
>
<
form
>
<
input
class
=
"input input--large mb-8"
>
<
button
class
=
"btn btn--primary mr-8"
>Submit</
button
>
<
button
class
=
"btn btn--pale ml-8"
>Cancel</
button
>
</
form
>
</
div
>
Scoped CSS
When it happens that we need element-specific styling, it's time for scoped CSS. This is not a failure of utility classes or modular CSS — it just means we're doing something interesting. Here, we target elements using liberally general selectors as we like, and style as necessary. Since the styles are scoped, they won't leak up into the rest of the document, and also won't leak down into nested components.
123456789101112131415161718192021 // component.vue
<
template
>
<
div
class
=
"card modal mt-24 mb-32"
>
<
header
class
=
"modal__header"
>
<
h4
class
=
"mt-32 mb-8"
>Enter your name</
h4
>
<
p
class
=
"mt-0"
>First and last name for your account</
p
>
</
header
>
<
form
>
<
input
class
=
"input input--large mb-8"
>
<
button
class
=
"btn btn--primary mr-8 submit-button"
>Submit</
button
>
<
button
class
=
"btn btn--pale ml-8"
>Cancel</
button
>
</
form
>
</
div
>
</
template
>
<
style
scoped>
header {
background: linear-gradient(cyan, magenta);
}
</
style
>
Vue has built-in support for scoped styles, but what if we're not using Vue? React supports scoping through CSS Modules. Web Components frameworks like Lit and El use the Shadow DOM to leverage direct browser support for scoping CSS.
All together now
This is the way — utility CSS for spacing and layout, modular CSS for standard UI components, and scoped CSS for the rest. In the end we get much of the convenience of Tailwind, without the explosive sprawl of classes in the markup. We get the consistency of modular CSS without having to litter verbose BEM classes in every context. And finally, we get the terneness of using simple, straightforward selectors without the risk of styles leaking out beyond where they're meant to apply.
Thanks for reading...
We make truly awesome collaboration tools for Microsoft Teams, and we'd love to show you around.