A Primer to Well-Structured CSS

CSS is a flexible language, but the same flexibility that makes CSS easy to learn also allows us to use it in ways that are unstructured.

Have you ever inherited a CSS codebase from another developer, only to find that you couldn’t understand how it was organized? Have you ever fixed something in one part of your site or app, only to have a completely unrelated page break? Have you ever spent 30 minutes going through your stylesheet(s) just trying to figure out where your new code should go? Most of us have struggled with these issues for decades, myself included. But since working at Highland and adopting the principles below, I have not had any of these things happen on a project I managed. Not once.

CSS is a flexible language, but the same flexibility that makes CSS easy to learn also has allowed us to use it in ways that are unstructured. The following will show a structured approach to CSS that makes it more maintainable and more usable. There are three areas that I will cover:

  1. Inverted Triangle CSS (ITCSS) Methodology
  2. BEM Namespacing
  3. Breathable and readable code formatting

I’d like to note that this guide is not comprehensive. The more experienced among you will wonder why I haven’t covered working with SASS, or partials, or utility-first CSS. I have chosen to focus on only these three techniques because I think that they are useful on every project, regardless of the tools you use or the size of the project.

1. Inverted Triangle CSS (ITCSS)

Of the methodologies covered here, the most foundational is Inverted Triangle CSS (ITCSS). ITCSS solves a really big problem: it shows us how to structure Cascading Style Sheets to make them actually cascade. It is not a framework or a library, but simply a methodology.

The ITCSS methodology works by defining layers of CSS. In this Inverted Triangle graphic we see how CSS is organized into layers:

The top layers apply broadly and have low specificity, and the bottom layers apply narrowly and have high specificity. In the setup shown above, the CSS will be applied as follows:

  1. Settings — used with preprocessors and contains font, colors definitions, etc.
  2. Tools — globally used mixins and functions. It’s important not to output any CSS in the first 2 layers. Putting things that apply broadly to our code in the first two layers keeps our CSS “DRY”.
  3. Generic — reset and/or normalize styles, box-sizing definition, etc. This is the first layer which generates actual CSS.
  4. Elements — styling for bare HTML elements (like H1, A, etc.). These come with default styling from the browser so we can redefine them here.
  5. Objects — class-based selectors which define undecorated design patterns. This allows us to have all the image objects to have a set of styles applied to them, that can then be overridden by component-level CSS for specific styling.
  6. Components — specific UI components. This is where a majority of our work takes place and our UI components are often composed of Objects and Components.
  7. Utilities — utilities and helper classes with the ability to override anything which goes before in the triangle, eg. hide helper class.

This is a great example of how ITCSS can be used on most projects. However, it is important to see this as a set of principles, not a prescription. It is meant to be adaptable.

For instance: need a theming layer? Insert one between the Components and Utilities (or after utilities, depending on your structure). Or if your project doesn’t use a pre-processor the Settings and Tools layers might go away entirely, since mixins and variables are not relevant.

For more:

2. Block-Element-Modifier (BEM) Namespace

Have you ever felt stalled trying to figure out what to name the class for an element?

You can name a CSS class anything you want, but again, the flexibility of naming within CSS comes with caveats. Block-Element-Modifier (BEM) is a namespace that creates clear relationships within CSS selectors and makes choosing a name for a given selector much easier.

The premise of BEM is that there are three levels of naming:

Block
Encapsulates a standalone entity that is meaningful on its own. While blocks can be nested and interact with each other, semantically they remain equal; there is no precedence or hierarchy. Holistic entities without DOM representation (such as controllers or models) can be blocks as well.

Example:

.block

Element
Parts of a block and have no standalone meaning. Any element is semantically tied to its block.

Example:

.block__element

Modifier
Flags on blocks or elements. Use them to change appearance, behavior or state.

Examples:

.block--modifier
.block__element--modifier

Bonus: Add pre-selectors to be Super Effective!
Pre-selectors magnify the clarity of BEM and allow you to code even more confidently.

  • o- : Object
  • c- : Component
  • u- : Utility
  • is- has- was- : Is styled a certain way due to current state or condition.
  • js- : Signifies that front-end js binds to this to provide a certain behavior.
  • qa- : Indicates QA is running automated tests that bind to this DOM element.

For example, a usable component block would have a `c-` in front of it to indicate it’s a component:

.c-block

This becomes a lot more powerful when using more flexible and/or complex components. Let’s say we have a button, but we want to modify it to have different colors depending on the state. Our HTML would look like this:

<button class="c-button">Standard Button</button><button class="c-button c-button--yay">Success Button</button>

Or maybe our component is a little bit more complex and requires several layers of sub-components. Here you can see how BEM describes the hierarchy of elements within the class names:

<div class="c-modal">
<div class="c-modal-header">
<h1 class="c-modal-header__title">Title</h1>
</div> <div class="c-modal__body">
<p class="c-modal__body__paragraph">Lorem ipsum dolor…</p>
<p class="c-modal__body__paragraph">Lorem ipsum dolor…</p>
<button class="c-button c-button--yay">Success Button</button>
</div>
</div>

For more:

3. Breathable and readable code formatting

If you’ve made it this far, you’ve got all the methodologies in place, but a few more rules of thumb will go towards making your CSS a joy to work with.

Never use IDs for styling
Ever. Only use classes. This will save you a lot of headaches.

Don’t over-indent
When working with pre-processors it’s easy to keep adding levels of nesting. Don’t. Generally, there should rarely be a need to nest your SASS/SCSS more than three layers — your BEM naming takes care of that for you.

White space is free — use it
White space is free when using a pre-processor since your compiler will strip it out. Yay! This gives us the ability to format our files more freely. It adds scrolling, but sacrificing scrolling for readability is an easy choice.

The format we are using on my current team is 1–2–4; one line of spacing common, two lines of spacing less so, and four lines of spacing to separate significant chunks of code. (We have no idea where we got this from but it works great.)

Document, document, document
Always create documentation, unless code structure is so clear that a description is unneeded. The latter is preferred, but the former is inevitable.


Well-structured CSS makes all the difference. Whether you are writing a complex utility framework, a website with sprawling pages, or a simple app, even experienced CSS devs will find a new level of mastery with these concepts under your belt.

Software is meant to be designed — not just developed. Learn more about how Highland’s team of makers, designers, and strategists can help bring your product ideas to life.