8th Light. 8th Light logo

A maintainable, scalable front-end

If you're like me, you have a love-hate relationship with styling.

Probably because along with HTML, these languages seem to force you to enact poor development habits. You'd like to keep things DRY, but unfortunately, standard CSS isn't very conducive to reducing repetition.

Isn’t there a better way? (cue B&W infomercial agony here)

Well yes—preprocessors like Sass and Less help developers create functions and variables just like other languages so that you can create reusable chunks of code, reducing the number of files you or future devs have to maintain.

However, with legacy codebases, we might not have the luxury to refactor everything. Rather, we can take steps to write cleaner front-ends, writing more semantically, with a human-readable structure designed for maximum scalability.

In this article, I’ve outlined a number of simple and advanced techniques around styling that can help you build a maintainable front-end, regardless of whether you're building a basic single-page app, or a robust app in React, Vue, or something else.

On Naming

Semantic HTML

<div> and <span> are intended to be the last element containers you should use after you’ve considered the available alternatives. Is this the <main>content of the page, or a <section> of it? Are we making a component that is <aside> that main content, or is it functionally a <nav> component?

Using the right HTML tag not only increases maintainability, but it’s also important for accessibility. Assistive tech like screen readers have a much easier time understanding what’s going on and users are able to skip ahead to the important parts when there’s proper naming applied to your HTML structure. This saves you time, as there's accessible functionality built right into the HTML elements.

Take a look at the Mozilla HTML elements reference for a well-organized view of all the existing HTML elements. You might be surprised to find what’s available to you. For example, do you know what the difference between bolding some text using <b> and <strong> is?

Semantic CSS

I’ll admit, this one can be a little challenging—especially if you have a shared CSS style document across many pages. Using better CSS names requires the ability to have both micro and macro understanding of the components' role in relation to its context. For me, it's helpful to pause and examine the designs or HTML to name the individual components before diving into styling. Then, we can take a look at how we chain those names so that parent-child relationships are clear and unique.

BEM Naming

The BEM (block__element--modifier) naming scheme is a popular option for its clear, distinct system for defining a selector. Essentially, a "block" defines a parent, be that a container or a thematic definition of a section. The element is a particular piece of that block. Therefore, we separate the block from the element with a double underscore: block__element. Take a look at the following:

<div class="enrollment">
  <h3 class="enrollment__header">Enroll!</h3>
  <button class="enrollment__button">Enroll Now</button>
</div>

If this component appears elsewhere with mostly the same styling but with some modifications, we use the --modifier suffix. This modifier can be directly attached to a block, or to a block__element. This can be useful if your design has multiple themes, like a dark theme.

<div class="enrollment--sidebar">
  <h3 class="enrollment__header--sidebar">Enroll!</h3>
  <button class="enrollment__button--sidebar">Enroll Now</button>
</div>

You might be familiar with the way that some boilerplate libraries use CSS, by chaining multiple classes within the HTML.

eg: <section class="lg-3 md-6 sm-12"></section>

Tailwind is another recent iteration on this theme. However, a core facet of maintainable code is the separation of concerns. When we start to style within the HTML, we are violating that separation, and things can get messy very quickly. Managing multiple breakpoints for different screens means more and more selectors, and changing multiple similar HTML elements becomes a nightmare.

In BEM methodology, I suggest using a single, semantically accurate selector so that you give it a role, and focus on any styling within your CSS. Even if the styling matches another part of your website, we can use the powerful SCSS@include, @extend, or @mixin](https://sass-lang.com/documentation/at-rules/mixin) to import and extend styling, rather than trying to compose styles in your HTML.

Dividing up your CSS into their roles

Your front-end should now start to look more readable, with roles clearly stated by the class selector, and for the most part, only one selector per element.

CSS also has a variety of functions that fall into different categories. We can structure it to separate styling into different files using a method called Inverted Triangle. At this level, it's important to note that this method works best with a preprocessor like Sass or Less.

There are a couple of structuring methodologies that I looked into—OOCSS, ITCSS, Atomic CSS, BEM, and SMACSS. Regardless of the method, the purpose is the same. To organize files based on their specificity, with the broadest items at the top—like tokens, functions, and mixins—cascading all the way down to the smallest, most specific selectors.

Inverted Triangle

1. Non-rendering abstractions

  • Settings – Preprocessor tokens, fonts, color definitions, etc.
  • Tools – Globally used mixins and functions.

2. Generic baseline styles and reusable components

  • Generic – Normalize or resets that set the baseline state for the CSS
  • Elements – Generic styling of bare HTML elements, like html, a, input, etc.
  • Objects – Class-based selectors that define a generic, reusable chunk of style. For example, a 3 column layout that becomes 1 column on mobile.

3. Specific components and overrides

  • Components – The bulk of the styling is here. Defines individual components, typically by section, page, or a functionally complex component.
  • Utilities – Anything that needs to be overridden, like helper classes that get attached to elements that need to be hidden (display: none !important)

A little bit of structure goes a long way to make styling much easier to find, and therefore make tweaks to components, saving everyone time and money.

Composing components using Sass

Now that we have these separations, we can compose class selectors to do exactly what we want. Say, for instance, we have a three column image component.

In settings/tokens.scss we have:

$color-primary: #ef5f32;
$spacing-small: 0.8rem;

In objects/layouts.scss

.three-columns-to-full {
  display: grid;
  grid-template-columns: 1fr; // one column

  @media #{$medium-up} {
    grid-template-columns: repeat(3, 1fr); // three columns
  }
}

and in our component/image-viewer:

.image-viewer {
  @extend .three-columns-to-full;

  color-background: $color-primary;
  padding: $spacing-small;
}

Our component takes advantage of reusable components built in the higher tiers of the inverted triangle, and now when the client wants to change the primary color, you only have to change it in one place!

I hope you're able to use these systems to ensure maintainability and scalability, on your project or the next one. Just being aware of the different syntactical ways we can improve our code can help make sure that you in the future, or the coder after you, can quickly get up to speed so that your product succeeds in the long run.

Hugh Sato, Designer

Hugh Sato is a Designer at 8th Light in Chicago.

Interested in 8th Light's services? Let's talk.

Contact Us