DEV Community

BezPowell
BezPowell

Posted on • Originally published at pinopticon.net

In defence of "semantic CSS" and the separation of concerns.

We are currently in the process of redesigning our website at work. One of the many conversations that came up was what frontend framework to use. Tailwind, inevitably, was one of those suggested and I had to marshal my thoughts on why I thought it was a very poor choice while still remaining civil. Others, such as Jared White and Aleksandr Hovhannisyan have written eloquently on many of the shortcomings of utility class CSS frameworks, but I would like to focus on a single aspect of the argument in favour of (and against) them: the separation of concerns between HTML and CSS.

This, then, is partially an article about why I think utility class CSS frameworks are a poor choice, but it's primarily an article about how I feel we may have misrepresented the separation of concerns. Any opinions, of course, are my own.

What is a utility class framework?

First of all, what is a utility class framework? In his blog post introducing tailwind css Adam Wathan, Tailwind's creator, gives a good overview of the differences between "semantic" CSS and utility class frameworks. I recommend you read this article if you have not already done so, as it will give you a good idea for the arguments in favour of utility class frameworks. I will only briefly touch on a few of the points Adam makes here.

In a nutshell, semantic CSS is how you were taught to use CSS. You have styles for generic elements, that are then overridden for particular components. Most importantly, you should try, wherever possible, to avoid contaminating your HTML with any design information; all class names should describe what an element is, not what it looks like.

An example of a button element might be:

<style>
    .button {
        display: inline-block;
        padding: 0.5rem 1.5rem;
        border-radius: 0.25rem;

        background-color: rgb(37 99 235);
        color: rgb(255 255 255);

        font-weight: 500;
        font-size: 0.75rem;
        line-height: 1.25;
        text-transform: uppercase;
    }

    .button:hover,
    .button:focus {
        background-color: rgb(29 78 216);
    }

    .button:active {
        background-color: rgb(30 64 175)
    }
</style>

<button class="button">Button</button>

Enter fullscreen mode Exit fullscreen mode

A utility class framework, in contrast, operates on the principle of having classes perform a single function, such as setting a certain amount of padding, or converting an element into a flex-container. In order to build up the styling for an element, you need to combine multiple classes together. In this methodology, of course, HTML and design are very closely coupled; most classes in a utility class framework are named more or less after what they do. An example of the same button created with Tailwind is:

<style>
    .inline-block {
        display: inline-block;
    }

    .px-6 {
        padding-left: 1.5rem;
        padding-right: 1.5rem;
    }

    .py-2 {
        padding-top: .5rem;
        padding-bottom: .5rem;
    }

    .bg-blue-600 {
        background-color: rgb(37 99 235);
    }

    .text-white {
        color: rgb(255 255 255);
    }

    .font-medium {
        font-weight: 500;
    }

    .text-xs {
        font-size: .75rem;
        line-height: 1rem;
    }

    .leading-tight {
        line-height: 1.25;
    }

    .uppercase {
        text-transform: uppercase;
    }

    .rounded {
        border-radius: 0.25rem;
    }

    .hover\:bg-blue-700:hover {
        background-color: rgb(29 78 216);
    }

    .focus\:bg-blue-700:focus {
        background-color: rgb(29 78 216);
    }

    .active\:bg-blue-800:active {
        background-color: rgb(30 64 175);
    }
</style>

<button
    class="inline-block px-6 py-2 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded hover:bg-blue-700 focus:bg-blue-700 active:bg-blue-800">
    Button
</button>
Enter fullscreen mode Exit fullscreen mode

For a single component, like this, utility class frameworks obviously produce a lot more code. The idea is that, by splitting classes up to only affect a single property, CSS code duplication is massively reduced on large websites with lots of components sharing common declarations.

Adam makes many more arguments in favour of Tailwind in his blog post that are out of the scope of this piece to go into, but the one that most interested me was his idea that.

I had "separated my concerns", but there was still a very obvious coupling between my CSS and my HTML. Most of the time my CSS was like a mirror for my markup; perfectly reflecting my HTML structure with nested CSS selectors.

My markup wasn't concerned with styling decisions, but my CSS was very concerned with my markup structure.

Maybe my concerns weren't so separated after all.

The argument that Adam is making here is that, especially on large and complex sites, purely semantic CSS is bound to start to reflect the underlying HTML structure. The example he gives is this one for an author bio card component.

<style>
    .author-bio {
        background-color: white;
        border: 1px solid hsl(0, 0%, 85%);
        border-radius: 4px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        overflow: hidden;
    }

    .author-bio>img {
        display: block;
        width: 100%;
        height: auto;
    }

    .author-bio>div {
        padding: 1rem;
    }

    .author-bio>h2 {
        font-size: 1.25rem;
        color: rgba(0, 0, 0, 0.8);
    }

    .author-bio>p {
        font-size: 1rem;
        color: rgba(0, 0, 0, 0.75);
        line-height: 1.5;
    }
</style>

<div class="author-bio">
    <img src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
    <div>
        <h2>Adam Wathan</h2>
        <p>
            Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent
            podcast and has never had a really great haircut.
        </p>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

If our CSS inevitably comes to reflect the HTML, why not write it in a way which minimises duplication and only allows developers to pick from a pre-set selection of values when writing their styles? That, in essence, is the argument in favour of utility class frameworks. The "best practice" of separating content and styling doesn't work in the real world and, therefore, any methodology should be considered on it's other strengths and weaknesses.

HTML != CSS

The above argument, and all other arguments I have seen in favour of utility class frameworks, seems based upon an unspoken assumption: that all violations of the separation of concerns between HTML and CSS are equally bad, regardless of their direction. On the face of it, this seems like a reasonable argument; we're supposed to keep content and styling separate so, if doing things the correct way, our styling inevitably comes to closely resemble our content, why should we not do the opposite if it will give us potential benefits?

An examination of the differences between HTML and CSS, however, will reveal that the situation is a little more complicated than that.

HTML is a language for defining the content of a page. CSS is a language for styling the content of a page. We can visualise this difference.

3 variations of a set of nine buttons. On top: styled with different colours. In the middle: blank white space. On the bottom: Plain grey with default browser styling.

The bootstrap button example. On top with HTML and CSS. In the middle with CSS only. On the bottom with HTML only.

At the top of the above image we have the default bootstrap button example with both HTML and CSS loaded. In the middle we have the same example without any HTML for the buttons. On the bottom we again have the same example, but this time without loading the CSS. As you can see, CSS without content to style renders absolutely nothing; as an extension to HTML, it is entirely reliant upon the content to work. HTML without CSS, on the other hand, just looks a little… ugly and, if you are a bot or screen reader, even this limitation is effectively moot.

HTML and CSS are not of equal importance to a web page. Without content a page is useless, without styling it just looks a little dated to our current expectations and could be less user-friendly. If that is the case, why should moulding one to fit the other be considered the same? Surely, modifying your CSS to fit your HTML is a lesser sin than the other way around?

Content is King

Now, more so than ever, viewing our website on a screen is only one way in which users may interact with our content. Bots and assistive technologies have been around for almost as long as the web, but reader views, social media syndication, IoT devices, and having pages read aloud are becoming increasingly common ways in which people interact with the web. All of these care very little – if at all – about the page’s CSS. The page content, and the HTML that defines it is what is important, and modifying it to suit styling may result in a sub-optimal experience for all the users who aren’t browsing the site on the same kind of device as the developer(s) who built it.

If we are again to look at the example from before, we will see that the two violations of the separation of concerns are not equivalent. Semantic CSS’ use of descendant selectors is a less serious offence than Tailwind’s pollution of HTML with class names. After all, that’s how selectors in CSS work, they bind to HTML elements.

One argument in favour of Tailwind, however, is spot on: no website exists in a perfect world. If we were to take the importance of HTML over CSS to its logical conclusion, we would not add any classes to our HTML at all. This would result in bloated, unmaintainable CSS that needs to be updated whenever the content structure changes; the exact equivalent of the bloated, unmaintainable HTML that needs to be updated whenever styling changes produced by utility class frameworks such as Tailwind.

We should always strive to make both our HTML and CSS as independent, concise, and maintainable as possible. Just, when one of them inevitably has to be moulded to fit the other, we should try and change the styling first. After all, that’s what it’s designed to do: add styling to your existing content.

The overlapping circles of a venn diagram that we might be used to viewing the relationship between HTML and CSS as are perhaps not the best way of visualising this relationship. Something like a house might be better, with the content as the ground floor, and the styling as the first storey. Where possible we try and fit the upper floors to the base, but certain affordances must be made, such as strengthening the walls to support further floors above.

Somewhere in-between the hells of utility class frameworks and class-less HTML there is a paradise where we have to make as few compromises as possible. That paradise probably looks a little like component-based CSS. We make as few changes as possible to our HTML to give CSS room to hook into, and we re-use as much related CSS as we can. After all, it’s in our and our users’ best interests to keep both as simple as possible. Because we already have to write sub-optimal code, seems like a very poor justification for throwing out best-practices altogether.

A final word on performance

One of the advantages put forward in favour of tailwind is to do with performance. Semantic CSS, it is claimed, can get rather bloated with duplicated style declarations shared between components. In contrast to this, a utility class framework, with the proper frontend tooling, can remove all unused declarations and result in much smaller file sizes, as a small number of classes are reused over and over again. The trade off, of course, is slightly increased HTML sizes and parsing time but, overall, the total size of content + styling is smaller.

This can of course be true, but once again assumes that HTML and CSS are equivalent when they are not. CSS rarely changes and, with a proper caching strategy, can be downloaded once, then used across multiple pages. In contrast to this, HTML is by its very nature unique to each page.

Tailwind claims that, with the correct tooling and compression, pages built with it rarely require more than 10kb of CSS, even for very large sites. Personally, I have built plenty of sites using bootstrap (not a particularly performance focused framework) without extensive tooling that send less than 30kb of CSS to the user; simply by importing only the components that I am using. An approximately 300% increase in size is obviously quite a lot, but in the grand scheme of things 20kb is very little when that is cached across subsequent page loads.

This is not to condemn Tailwind and other utility class frameworks in any way. Serving less than 10kb of CSS for even very large sites is an impressive achievement, and something we should all be aiming for. I just feel that that is less to do with the framework chosen, and more to do with caring about performance in the first place. Using tailwind without optimisations will result in huge CSS files, just as will downloading the entirety of Bootstrap or Foundation; neither will ever be as performant as hand-written CSS by someone who knows what they’re doing.

In conclusion

What I hope to convey in this post is that content and styling are not of equal importance to a web page, and we should not be basing our front-end methodologies on that assumption. Utility class frameworks such as Tailwind do bring many benefits in terms of developer productivity, but those benefits must be weighed against the downsides of moving away from established best practices.

At the end of the day, the purpose of a webpage is to communicate information to users. Not every user will interact with our pages in the same way, and we should build them in such a fashion that they support as many devices and means of interaction as possible. Developer experience is, of course, important, but we should not blindly pursue it at the cost of user experience.

For now at least, I'm going to stick with semantic CSS and separating my styling and content as much as possible. You may do what you like, but I hope that after reading this you may reconsider the importance of the separation of concerns and best practices if you have already decided that they are less important than development speed.

Top comments (3)

Collapse
 
alohci profile image
Nicholas Stimpson

I've been re-reading the early discussions around the creation of CSS and it reminds me why it's designed to use selectors to bind the HTML to the styling. It's clear that separation of concerns is not the end in itself, but a means to the end of being able to apply multiple presentations to the same content.

The basic principle is that the life-cycle of content, and the life-cycle of styling is not the same. i.e. That content will have a lifetime that will be substantially greater than its presentation, so hard-coding the styling into the HTML as utility-first frameworks do, complicates the maintenance process. At any given time, the content can only have a single styling, and changing the styling means editing all the content.

Having said that, I don't think you've got the principles quite right:

all class names should describe what an element is, not what it looks like.

Class names should describe how the content is categorised. This is a somewhat difficult concept to teach, because it can't be divorced from actual content. The class names you use will be coupled to the domain knowledge of the content that the web site wants to convey.

For example,. this means that "button" is typically a poor choice for a class name. We already have a button element that's almost always the right choice of element for something that acts like a button on a web page. So a class name of "button" adds nothing to the semantics. On the other hand, "button" might be a good choice of class name on a web page about sewing or tailoring.

You say "HTML != CSS" which is true of course, but in my opinion the key distinction when addressing the matter of CSS being over connected to the markup structure is that Selectors != CSS. The mistake we make is to regard selectors as part of CSS. We should regard Selectors as the tool which binds between HTML and CSS. Think of CSS as the declaration blocks. It's those which controls our themes, our spacing, our shadows, our vertical rhythm and so on. In this way we see that the CSS is not in fact tied to markup structure.

Overall, an excellent article though that covers the issues in a well balanced way.

Collapse
 
bezpowell profile image
BezPowell

Wow, what a comment!

Thank you so much for taking the time to reply with such a thoughtful comment; it's definitely set wheels turning in my head. The button class, I agree, was probably a poor choice of an example. I'm currently re-writing the CSS for my personal website and am not using a class for buttons, just targeting button elements directly, as I recognise it as a bad habit I've picked up from frameworks like Bootstrap (why oh why do so many tutorials throw people straight into learning frameworks? It teaches you bad habits you only realise are bad years later). I suspect they arose as a way of avoiding conflict with existing styles.

Your point about Selectors != CSS is a beautiful way of expressing something I'd not fully considered before. I'm used to seeing class names in HTML as an affordance we add to allow styling to 'hook in', but no one had ever mentioned to me that selectors are the direct parallel in CSS. I feel like my understanding has just jumped up a level and will approach future projects in a slightly different way from now on.

Thanks again, writing the article was definitely worth it for me as it sparked such a productive conversation.

Collapse
 
aarone4 profile image
Aaron Reese

Tailwind gives you the flexibility to do both with the @apply functionality; this allows you to combine multiple TW base CSS classes into a single named class but then also allows you to override these settings for a specific element where necessary.

One aspect of utility frameworks I find frustrating is having redundant markup code that does nothing except act as a wrapper with CSS elements around actual content, but I guess that is where web components come in handy.