Keeping CSS maintainable

Advanced

Introduction

Writing CSS for one page is easy. Keeping it working after two years and many developers is hard, because any rule can affect any element. Most of the techniques in this lesson exist to bring that risk under control. They matter more as a project grows.

How it works

A small reset. Browsers apply slightly different default styles. Many projects start with a few rules that remove the most troublesome ones:

*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
img, svg { display: block; max-width: 100%; }

Prefer a small reset over a huge one, because each rule that you add is one more thing to remember.

Naming. Without a system, two developers will each create a .title class, and they will clash. BEM is a popular solution, which names things as block, element and modifier:

.card { }
.card__title { }
.card--featured { }

A block is a standalone component. An element is a part of it, joined with two underscores. A modifier is a variation, with two dashes. It looks odd at first, but every selector is a single class, so specificity stays flat, and the class name tells you exactly where it belongs.

Utility classes. Another approach, used by Tailwind CSS, is many tiny single-purpose classes such as mt-4. Both approaches work, and the worst option is having no approach.

Cascade layers. Using @layer you can set the priority of whole groups of rules:

@layer reset, components, overrides;

@layer components {
  #app .button { color: gray; }
}
@layer overrides {
  .button { color: teal; }
}

The order of the layer names in the first line decides who wins. Layers later in the list beat earlier ones, regardless of specificity. So .button above wins, even though #app .button is much more specific. Styles in no layer beat all layers. This makes it safe to bring in third-party CSS.

Nesting. All current browsers support nesting, as preprocessors have done for years:

.card {
  padding: 16px;
  & .title { font-weight: bold; }
  &:hover { background: whitesmoke; }
}

Keep nesting shallow, since each level adds to the specificity.

Debugging. Open your browser's developer tools and inspect an element. The Styles panel lists every rule that applies and strikes out those that lost. You can edit values live, tick declarations on and off, force a :hover state, and see the box model. The Computed panel shows the final value of each property. Temporarily adding outline: 1px solid red to everything is a quick way of finding what is causing sideways scrolling.

Syntax

@layer base, components;
@layer base { a { color: navy; } }

Example

console.log output from the page shows here.

Line by line

  1. The first line sets the order: overrides beats components.
  2. The first rule has an id and would normally win.
  3. The second rule is in a later layer, so it wins regardless.

Expected result

A button whose text is teal rather than crimson.

In the real world

Large teams enforce conventions with linters such as Stylelint. Frameworks have started shipping their CSS in layers so that your own styles win without a struggle. Many production bugs are found within seconds using the Styles panel.

Common mistakes

  • Nesting deeply and raising specificity.
  • Mixing several naming systems in one project.
  • Declaring layers in the wrong order.
  • Debugging by guessing, rather than inspecting the element.

Best practices

  • Choose one naming system and write it down.
  • Keep a shallow, flat stylesheet.
  • Remove styles when you remove the feature they served.
  • Reach for developer tools first.

Summary

A short reset removes inconsistencies, and a naming system such as BEM keeps classes from colliding. Layers rank groups of styles above specificity, nesting tidies related rules, and developer tools show exactly why a style applied.

Quick check

1. In BEM, which class names an element of a card?
2. Two rules are in different layers. What decides the winner?
3. Where can you see which rules lost, and why?

Key takeaways

  • Start each project with a short reset.
  • A consistent naming system stops classes from colliding.
  • A layer's order beats specificity.
  • Developer tools show which rule won, and why.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.