Skip to content

Well-Structured, Clean CSS Without Dependencies

See how you can write maintainable, clean, and clutter-free CSS with some simple tricks.

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 a subjective thing — everybody defines and sees it differently. Whenever I code something, I don't just strive to make my applications look beautiful on the outside but also on the inside. Having ugly, bloated, or poorly documented code is a no-go for me.

However, especially in HTML and CSS, I have recently noticed a shift. Since major libraries like React have gained popularity, the traditional 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:

  • Having to install and ship dependencies, increasing app complexity and bundle size. Also, having to learn each libraries' API, syntax, and quirks, although I just want to style something.
  • The generated output is often messy, with unreadable class names and many unnecessary overrides.
  • Major version updates may require some work since they can introduce breaking API changes.
  • Inconsistent architecture. The product I work on uses Material UI, styled-components, and LESS. In other words, three different ways of writing CSS, and I don't particularly appreciate having to use them all together instead of just one. I also witnessed specificity issues between these since 2 CSS-in-JS engines inject their styles into the document's head without knowing which one is overriding the other.
  • It feels weird to write CSS in JavaScript without proper syntax highlighting out of the box (and yes, I know there are solutions available, but it still doesn't feel right).
  • Having less control over the outcome. styled-components, for example, automatically adds vendor prefixes, which I don't need since I don't want to support older browsers. Why can't I disable them?
  • Styles are added at runtime through JavaScript, meaning there can be a performance impact if the page is not server-side rendered.

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 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. So 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 rscss. This is not a framework or 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, look at BEMSMACSS, or Atomic CSS. All of these are methodologies rather than ready-to-use frameworks, and each provides its own set of ideas on how a CSS codebase should look.

I had 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 the 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, double-dashes, or 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 achieved the same thing, but it looks a lot clearer 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 two words, separated by dashes. Element classes inside a component, such as a logo or links, should only have 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 modified with something else. Modifiers are useful for different variants of buttons or other components.

Another common thing in CSS is helper classes. A helper class should only have one purpose: 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 not to 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 make changes.

There are many great tutorials 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 aligns with your project. You might not always need a themes folder, so skip it. You might not want 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 love SCSS, I find that I didn't need it anymore in recent projects, 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 can now work with it, which gives us an opportunity. Jake Archibald has a nice blog post going into the details and providing some insights.

These are the advantages I have made out personally:

  • There's no need to "compile" single CSS files into a big one since we can directly use them in our HTML.
  • Thanks to HTTP 2, loading many smaller files is more performant than loading a big, single one.
  • We only load the styles needed. If I'm on the "About" page, I just include CSS from about.css and maybe a few components or global styles. This improves page loading and rendering.
  • It's clearer what CSS belongs to a particular part of the HTML. Putting the link above your header will most likely tell you that its styling is inside this very file, instead of having to look through a potentially big folder first.
  • Thanks to CSS custom properties, I usually define my variables in global.css, which is loaded on every page inside the . I now have access to them everywhere on my site, no matter which folder or file I am currently in. I can even inspect and change them in DevTools, which is impossible with SCSS or LESS.

Grouping CSS Properties

One more step to making 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 block:

.button {
  display: flex;
  margin: 0;
  color: tomato;
  border: 2px solid tomato;
  background-color: white;
  font: inherit;
  transition: all 0.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 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 0.2s ease-out;
}

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

  • Layout (flexbox, margin, padding, height, width)
  • Position (position, top, right)
  • Colors (color, background-color, blend-mode)
  • Fonts (font-family, font-weight, text-transform)
  • Borders (border-width, border)
  • Others (transition, appearance, animation, opacity)

Bringing It All Together

Let's 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 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 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 inspire you.

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

My main reasons for using said approach are that it uses 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.