Sass mixins and the question of maintainable CSS

One of the easiest ways to make a stylesheet more complicated is to start tidying it before deciding what the repetition means. Two sections use similar spacing, so I create an abstraction. A third section is slightly different, and the abstraction grows another option.

I like Sass, but I want a mixin to explain a decision. Saving a few lines in the source is not enough if the next person has to inspect several files to work out which CSS reaches the browser.

In this tutorial: What we are building; Before you start; 1. Keep the mixin's responsibility small; 2. Distinguish a value from a recipe; 3. Read the generated CSS; Check the behaviour.

What we are building

A small mixin for an agreed minimum-width breakpoint. It wraps a component's own rules in a media query, keeping the component readable without repeating the query syntax everywhere.

Before you start

The example uses Dart Sass and its module system. The @use rule is not supported by LibSass, so check the actual compiler used by the project rather than assuming every package called Sass accepts the same features.

I also want a reason for each breakpoint. A framework's device names can be convenient, but the point at which a component stops working is more useful than a label such as tablet.

1. Keep the mixin's responsibility small

// _breakpoints.scss
@mixin from($minimum) {
  @media (min-width: $minimum) {
    @content;
  }
}
// article-list.scss
@use 'breakpoints';

.article-list {
  display: grid;
  gap: 1.5rem;

  @include breakpoints.from(48rem) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

The mixin answers one question: at what minimum width should these rules apply? It does not decide which component is a card, what its spacing should be or how every grid in the website must behave.

Using the module namespace also makes the source easier to follow. I can see where from comes from rather than relying on a large shared global namespace.

2. Distinguish a value from a recipe

If I only need to reuse a spacing value, a variable may be enough. If the value should remain adjustable in the browser, a CSS custom property may be more appropriate.

A mixin earns its place when it represents a small recipe or accepts a meaningful variation. I become wary when its parameters start describing an entire component: border, radius, shadow, display mode, colour scheme and five optional exceptions.

At that point I may be hiding several different components behind one name. A few explicit rules can be easier to maintain than an abstraction that accepts nearly every possible design choice.

3. Read the generated CSS

Including a mixin emits CSS where it is used. It does not automatically deduplicate every repeated declaration. A small source file can therefore produce a much larger stylesheet than I expected.

For a repeated structural class, I consider whether a normal CSS selector is clearer. For a complicated shared abstraction, I check whether all its consumers genuinely need the generated rules.

I also keep selector nesting shallow. Repeating the page's entire HTML structure inside SCSS makes it difficult to move a component without changing its appearance. The editor should be able to rearrange content without stepping on an invisible chain of ancestor selectors.

Check the behaviour

Compile the example and inspect the resulting selector and media query. Change the width around the breakpoint, use a long title and check that the grid still has room for its content.

Then read the source as if you did not write the mixin. Can you find its definition, explain its arguments and predict the output without following another chain of includes? That is a practical maintainability test, even before measuring the stylesheet size.

A useful mixin gives the team a shared way to express a repeated decision. I want it to make the CSS easier to understand, rather than make ordinary CSS look too simple to write directly.

References

Sass documents mixins and includes and the module system. Both matter when choosing syntax that the project's compiler can actually build.

More about me