Well-structured, clean CSS without dependencies

Well-structured, clean CSS without dependencies

Disclaimer: The following article reflects my personal opinion and experiences and is in no way meant to badmouth certain libraries, tools or developing styles.

Beauty is considered to be a subjective thing — everybody defines and sees it in a different way. Every time I code something, I don’t just strive to make my applications look beautiful on the outside, but also the code that powers them. Having ugly, bloated or badly documented code is a no-go for me.

However, especially in HTML and CSS, I have recently noticed a shift. Since mayor libraries like React have gained popularity, the classic ways of writing CSS and HTML are being swapped with tools like styled-components or emotion, commonly known as CSS-in-JS.

What’s the issue?

But the more I work with these technologies, the more I miss the “good old” days where HTML was just HTML and CSS was just CSS. In my current job, the team I work with uses React and styled-components, which powers our entire frontend. It works, but there are certain things I dislike:

I am fully aware that these are not always problems of the tools themselves (which also keep improving day after day, just look at the latest release of Material UI), but rather how we decide to use them. Still, I feel like I don’t need these things to style my applications. Having features like conditionally rendered CSS is nice, but not that big of a deal for me.

That’s why — after gaining these experiences — I don’t use any of these technologies in my personal projects anymore, instead, I prefer a clean approach of clearly structured and separated CSS. Let’s have a look, shall we?

Naming things

A few years ago, when I was looking for ways to organize my CSS, I came across something called rscss. This is not a framework nor a library, but a collection of ideas that will help you to keep your CSS clean and well-organized.

A set of simple ideas to guide your process of building maintainable CSS. — https://rscss.io

If you want to compare it to something, have a look at BEM, SMACSS or Atomic CSS. All of these are methodologies rather than ready-to-use frameworks and each provides its very own set of ideas on how a CSS codebase should look like.

I have worked with the BEM approach before, but disliked how quickly your markup becomes bloated by long class names:

<a class="button button--state-danger button--state-disabled">
  <svg class="button__icon button__icon--size-big"></svg>
</a>

Although I appreciate the clear structure and visual separation of blocks, elements, and modifiers, I find above code ugly and just too much for what it achieves.

<custom-dropdown></custom-dropdown>

Look at the above example of a custom element. The HTML specification explicitly requires words to be separated by dashes; not underscores, not double-dashes, nor anything else. Ideally, I’d like my classes and ids to follow this scheme, even though technically it doesn’t matter.

Let’s look at the button example again, now with the rscss approach:

<a class="button -danger -disabled">
  <svg class="icon -big"></svg>
</a>

See how much clearer this looks? I have basically achieved the same thing, but it looks a lot cleaner to me now. More in line with the HTML, I’d say.

Structure with components

When using rscss, a good practice is to split your application into components. An example of a component could be the page’s navigation:

<nav class="main-navigation">
  <a class="logo" href="/">Company XY</a>

  <a class="link -active" href="about.html">About Us</a>
  <a class="link" href="products.html">Products</a>
  <a class="link" href="contact.html">Contact Us</a>
</nav>

Component classes should contain at least 2 words, separated by dashes. Element classes inside a component, such as a logo or links, should only contain one word. Try to avoid nesting HTML as much as possible, as it makes styling and semantics more difficult.

.main-navigation {
  > .logo {...}
  > .link {...}
  > .link.-active {...}
}

Element modifiers are marked by dashes in front of their names, such as -active. This syntax is oriented at how parameters are passed into the terminal on UNIX systems and means that the element’s base styles will be overridden with something else. Modifiers are useful for different variants of buttons or other components.

Another common thing in CSS are helper classes. A helper class should only have one purpose, such as aligning items or resetting margin. Helper classes in rscss begin with an underscore and don’t contain dashes: _nomargin or _aligncenter. The underline makes them look undesirable, which is the idea: they ought to be used sparingly across your codebase, as your layout should ideally be made up of reusable and configurable components.

Organizing the CSS

CSS can still get messy, especially once you have a few hundred lines of it. That’s why it’s a good idea to not have all of your CSS in one huge file, but rather many smaller ones.

If you have used SASS or LESS before, you are most likely familiar with imports. They easily allow you to separate your styles across as many files as you like, loading them all in on compilation:

@import "buttons";
@import "inputs";
@import "titles";

I highly recommend doing so. In my projects, I always try to enforce a descriptive folder structure, which could look like this:

css/
  components/
    buttons.scss
    inputs.scss
    headings.scss
  pages/
    about.scss
    index.scss
  base/
    reset.scss
    typography.scss
    variables.scss

My rule of thumb is that one CSS or SCSS file should only be responsible for one thing, for example, a button, the typography or the section of a page. This way, my files are kept small and maintainable, while I always know exactly where to look in case I need to do changes.

There are many great tutorials out there, giving advice and best practices on how to structure CSS, SCSS or LESS. My tip is to go for an architecture that makes you happy and is in line with your project. You might not always need a themes folder, so skip it. You might not want to have a stylesheet for each page, so don’t do it. Nobody is forcing you to exactly copy these ideas, but rather adapt and customize them.

If you want to read more about SCSS/SASS best practices, here you go.

Writing CSS without a preprocessor

While I absolutely love SCSS, I find that in recent projects I didn’t need it anymore, at least when I didn’t have to support older browsers. That’s when I stuck to plain CSS, which recently has introduced features like variables (or better known as custom properties). Even nesting is coming, too!

But how do I compile all the single CSS files into one big chunk, ready to be served as the main styles.css? Well, I don’t!

Instead, I put my link tags to each file directly in the body of my HTML, where and when I need it:

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="/css/global.css">
  </head>
  <body>
    <link rel="stylesheet" href="/css/components/header.css">
    <header class="main-header"></header>

    <link rel="stylesheet" href="/css/components/navigation.css">
    <nav class="main-navigation"></nav>

    <!-- and so on... -->
  </body>
</html>

This technique was not valid in the past, as the HTML spec didn’t allow link elements inside the body (it’s still a gray area, though). However, browsers are now capable of working with it, which gives us an opportunity. Jake Archibald has a really nice blog post going into the details and providing some insights.

These are the advantages I have made out personally:

Grouping CSS properties

One more step to make your CSS more readable, especially if you have many rules in one selector, is to group your properties. There’s a great article on MediaTemple about grouping, showcasing different techniques one might like. Look at this selector:

.button {
  display: flex;
  margin: 0;
  color: tomato;
  border: 2px solid tomato;
  background-color: white;
  font: inherit;
  transition: all .2s ease-out;
  padding: 1rem 2rem;
  border-radius: 3px;
}

The CSS properties in the above example are completely mixed up by their type, meaning that layout (padding, flexbox) is not separated by color (color, background) and so on.

I personally like to group my CSS properties by their type, so the CSS for the above example button would look like this:

.button {
  display: flex;
  margin: 0;
  padding: 1rem 2rem;

  color: tomato;
  background-color: white;

  font: inherit;

  border: 2px solid tomato;
  border-radius: 3px;

  transition: all .2s ease-out;
}

You can define what kind of grouping you’d like, I usually go with these 6:

Bringing it all together

Let’s have a look at a real-world example, using the above techniques. Say we have a page with a header, navigation, some content, and a footer:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>My Awesome Site</title>
    
    <link rel="stylesheet" href="/css/base/global.css">
    <link rel="stylesheet" href="/css/base/typography.css">
    <link rel="stylesheet" href="/css/components/buttons.css">
  </head>
  
  <body>
    <link rel="stylesheet" href="/css/layout/main-header.css">
    <header class="main-header">
      <a class="logo -dark">My Awesome Site</a>
    </header>

    <link rel="stylesheet" href="/css/layout/main-navigation.css">
    <nav class="main-navigation">
      <a class="link -active" href="index.html">Home</a>
      <a class="link" href="about.html">About</a>
      <a class="link" href="contact.html">Contact</a>
    </nav>

    <link rel="stylesheet" href="/css/layout/main-content.css">
    <main class="main-content container">
      <h1 class="title">Welcome, enjoy this heading</h1>
      <p class="subtitle">What about this awesome subtitle? It seems to be a trend in recent web design, right?</p>

      <link rel="stylesheet" href="/css/components/gallery.css">
      <figure class="gallery-wrapper">
        <img class="image" src="/images/one.jpg" alt="One">
        <img class="image" src="/images/two.jpg" alt="Two">
        <img class="image" src="/images/three.jpg" alt="Three">
      </figure>

      <a class="button -small -primary" href="about.html">Learn more</a>
    </main>

    <link rel="stylesheet" href="/css/layout/main-footer.css">
    <footer class="main-footer">
      <p class="copyright">&copy; 2019 by Some Company Inc.</p>
    </footer>
  </body>
</html>

A rather simple example, of course, but you should get the idea. In global.css, I define my colors, spacing, fonts and so on:

:root {
  --color-red: #f00;
  --color-blue: #00f;
  --spacing-large: 1rem;
  --font-family: 'Open Sans';
}

body {
  margin: 0;
}

...

My other CSS files are written using the earlier described rscss approach, which might look like this:

.main-footer {
  background-color: gray;

  font-size: 90%;
}

.main-footer > .copyright {
  text-align: center;
}

If you definitely need some nesting or fancy mixins, feel free to use a CSS preprocessor. They can still be used with this approach and might provide even more goodies, such as the awesome color functions.

Conclusion

I know that this might be a controversial article because, in the end, everybody is different and loves to use different tools, approaches, and philosophies. My goal is not to make you like my approach to scalable and beautiful CSS, but rather to give you inspiration.

Keep in mind that the bottom line is not to say that CSS-in-JS is bad, or that BEM was a stupid idea; they all exist for good reason. Very talented people have brought them to life, and a big community is using them to build amazing things each day. All I am saying is that I personally don’t enjoy them as much as I do enjoy my very own style, explained here.

My main reasons for using said approach is because it makes use of the latest web platform features, doesn’t require any additional libraries or tools, is scalable and maintainable and produces beautiful code.

However, what counts in the end is the final product we, the developers, ship to our users and that said product is accessible, performant, secure and in general, a joy to use.

Back