Are abstractions like style names and components worth it?
Mauro Bieg on November 27, 2025
Are WYSIWYG word processors, inline styles and Tailwind conceptually the same? How to make the best use of modern CSS and HTML elements? And what do we actually gain by adding abstractions like style names and components, and what do we lose?
There are many different ways to render text to pixels using a computer. Let’s go through some! We’ll start with the lower level ones (closer to the hardware, or at least to the characters), and then subsequently add more and more abstractions, also known as adding levels of indirection.
WYSIWYG
If you’ve ever changed the font-size and font-family of individual words or paragraphs of text in a word processor like Microsoft Word, you’re familiar with this.
It’s the same basic and direct interface like we’ve had several iterations of in web development:
- the deprecated HTML font element
<font size="7">Hi</font> - CSS inline styles
<span style="font-size: 20px">Hi</span>, - Tailwind:
<span class="text-xl">Hi</span>
The nice thing about it is that it gives you direct and immediate control over the text you’re looking at right now. The not so nice thing about it is that if you’re not careful, it quickly leads to an inconsistent mess of a layout.
Reusable styles
To solve these inconsistencies, we add our first level on indirection: we give names to different styles that we then reuse in different places. This ensures all those places look the same, even if we change the style.
In desktop publishing software like Adobe Indesign, you set up character- and paragraph styles. In LaTeX you add a bunch of macros. For the web, CSS was invented as a solution for this very problem – to replace the <font> tag. In web development circles, this idea is known as the “separation of concerns” – where you separate between content (written in semantic HTML), and layout/styling (written in CSS).
Reusable components
But people kept building ever more complicated websites, and chunks of HTML were repeated in various places, but slightly inconsistent. So we added another level of indirection: components (or in older templating systems called includes or partials) – blocks of several HTML elements that you can reuse in multiple places. (Conceptually similar to a function that you can call in multiple places.)
It’s a powerful, and often very useful, abstraction. In fact, it’s so powerful that people started to use it everywhere for everything. And since everything was a component now, people wondered why they still needed the abstraction that was CSS. If the only place you were using a <h2> tag in your whole codebase was in the <Title> component – then why not add the styles right there? That’s one reason why Tailwind is so popular.
And to be fair, if everything is a component, then why not? But should everything be a component? Do I need a <Button> component if I have a perfectly good <button> HTML element? The answer is, as always, it depends. Perhaps if it’s a very, very complicated button, with lots of different variants. But didn’t we want a consistent layout?
The one gripe I have with components is that they are usually a build-time abstraction: you don’t see them anymore in the browser’s dev tools element inspector. Unless you use web components, but then you have to deal with the shadow DOM, which is its own, hard to penetrate, level of indirection. Or unless you use your framework’s custom developer tools extension, but there you usually don’t see the HTML elements anymore, only your components.
Modern HTML and CSS
Meanwhile, HTML and CSS have not stood still. There are plenty more semantic HTML elements nowadays (like <main>, <header>, <footer>, <section>, <search>, etc.), and you can style them with attribute selectors, parent selectors, and much more. But to use those effectively, you need to be aware of exactly what HTML elements you have on your page, and how they’re nested. And if you overuse components, that abstraction makes it harder to see directly in your code what HTML you’ll have in your browser.
To be clear, I’m not saying you should never use components. But I’d recommend going with the least powerful abstraction that still solves your problem. And more often than not, this is semantic HTML with a little bit of CSS. Indeed, there is a danger with CSS that you can get yourself into a mess if you don’t restrict yourself enough. Since CSS is so powerful, sometimes you have to be mindful of every character. It pays off to adhere to a few principles like:
- Use the direct child combinator
>instead of the space wherever possible. (The space selects all children, regardless how deeply nested.) - Don’t invent new classes for everything, but use element selectors where possible. (Read this great piece by Heydon Pickering for elaboration of this point.)
- Take a bit of time to set up a few CSS variables, then restrict yourself to using those instead of magic numbers for spacing and sizes.
And sure, maybe you’ll need to factor out a few things to reusable components once your website grows. And you may even want to place their CSS file in the same folder as the component, and use the name of the component as a class. (In HTML5, class attributes are case-sensitive. So one way to mark your classes as “tied to a component” is to uppercase them.) But that doesn’t mean you need a bundler or baroque build step: if you have many CSS files and are worried about performance, you can just concatenate them.
Data and reusable templates
On another front, long before there was any talk about components, people wanted to reuse the same content on different pages. Possibly the simplest example is to reuse the titles of your blog posts on the index page of your blog. It would be annoying having to keep the index page and the detail pages manually in sync by always updating both, when you change the title of a blog post.
Thus, another level of indirection was introduced. Instead of having plain HTML files, you have some kind of separate data format: either a database, or maybe just plain markdown and YAML or JSON files.
As with any of the levels of indirection we’ve been talking about here, this especially shines for high-volume productions (i.e. your website has lots of content). And you want all that content to be laid out consistently.
Jess Eaton has a wonderful slide deck about CMSes with this grid in it:
| low volume | high volume | |
|---|---|---|
| consistent | piece of cake | structure, templates, APIs |
| unpredictable | custom design, development | here be dragons |
If you’re doing low volume, unpredictable stuff, all these levels of indirection might only get in your way. Why separate content and layout, and why reuse styles, if you only have a handful of things to style anyway? Then you might be better off just using a graphical tool like Webflow, or slapping those inline-styles or Tailwind classes on your stuff.
Tailwind
Did I just equate inline-styles with Tailfind? Basically, yes. Tailwind’s answer to why not just use inline styles? says something about a “predefined design system” (which you can easily set up with CSS variables as well), and about responsive/hover/focus (which are trivial in plain CSS). But they don’t seem to disagree with the claim that at least conceptually, Tailwind is equivalent to inline styles.
What Tailwind unfortunately does impose is a build step, which is another giant level of indirection. But there are plenty of utility-class CSS frameworks out there that don’t require a build step, if that’s what you’re after. Whether the elimination of unused CSS is worth the build step is for you to decide. But I wouldn’t sweat the CSS size too much. Consider how big your HTML payload is anyway with all those utility classes, static CSS usually has fairly long cache live-times set, and finally both compress well over gzip/brotli.
Levels of indirection
What’s the take-away of all this? You may have heard this quote:
We can solve any problem by introducing an extra level of indirection – except the problem of having too many levels of indirection.
The difficulty is knowing in which situation you find yourself today.
Let's keep in touch! 👨‍🍳
Follow us on Bluesky, or add our blog to your RSS reader (feed link).
This is the end of the page. Yet it may be the beginning of your journey with Mastro.