Stephen Lindberg

Product Designer @ Iterable

16 January 2021

Utility Classes I Have Known and Loved

I have yet to jump head-long into the hypetrain of utility-first CSS frameworks, but I’ve been enjoying the benefits of the ideology in small portions. The pros and cons of a framework like Tailwind compared to BEM are for another blog post. Today I’d like to share the indispensable utility classes that I carry with me from project to project.

First Up: Margins

Margins are notoriously persnickety. If not accounted for, they’ll break your layout alignment, ruin your padding, and sometimes seemingly disappear. This is why I avoid margin rules on component root elements. The component needs not concern itself with where it appears in relation to other elements in the layout. With Sass, a simple loop can help us generate several margin classes that give us the power to tweak margins on-the-fly as we’re authoring HTML:

$directions: "top" "bottom" "left" "right";
$sizes: (
  "zero": 0,
  "auto": auto,
  "xs": $size-extra-small,
  "sm": $size-small,
  "md": $size-medium,
  "lg": $size-large,
  "xl": $size-extra-large
);

@each $name, $size in $_sizes {
  @each $dir in $_directions {
    .m-#{$dir}-#{$name} {
      margin-#{$dir}: $size !important;
    }
  }
}

This will generate a margin rule for each combination of size and direction, such as m-top-lg or m-left-xs.

You could expand on this idea to create shorthand margin rules, such as a rule for all directions at once, or only vertical or horizontal directions:

@each $name, $size in $_sizes {
  .m-#{$name} {
    margin: $size !important;
  }

  .mx-#{$name} {
    margin-left: $size !important;
    margin-right: $size !important;
  }

  .my-#{$name} {
    margin-bottom: $size !important;
    margin-top: $size !important;
  }

  /* … */
}

This margin tool is useful for pushing elements around in those pesky cases where something doesn’t align with the layout in quite the way you’d like, or if you need to remove a margin from an element in a unique scenario.

I use !important in my utility classes because if the class is applied to an element, the rule should be applied without doubt. Another way to put it: another class should never override a utility’s effect. If that is desired, the utility class should simply be removed from the element.

The Stack

Flexbox is wonderful. With flex-wrap it provides a responsive container for our components, however there’s a small oversight: the missing gap rule. Like CSS Grid, flexbox has a gap rule which creates margins between each column and row, however gap is not yet universally supported for flex containers. We can create a workaround for this with a little negative margin hack, which I call a Stack:

$spacing: 1em;

.stack {
  align-items: flex-start;
  display: flex;
  flex-wrap: wrap;
  margin-left: -$spacing;
  margin-top: -$spacing;

  > * {
    margin: $spacing 0 0 $spacing;
  }
}

Each child has a positive top and left margin, while the parent element has a negative top and left margin which will cancel out the margins of the left-most and top-most elements in the stack. This is really useful for rows of buttons, tag labels, and almost anything else you can think of that needs a responsive container without conforming to the rigidity of a grid. It also plays nicely with other flex rules like justify-content: space-between;.

Typography

Typography is often subject to the context of the page or layout. It’s difficult to say with certainty that your <h1> should always be a certain size, or that the body text of given page is always 1rem. Instead of taking the BEM approach and creating a unique class like .homepage-hero-header for every variant, I find it’s much easier to compose a few typographic utility classes to achieve the result I want. The three utilities I use are for the rules color, font-size, and font-weight. These will be unique to your project since they depend heavily on your design system in terms of color and typographic scale. Mine usually look something like this:

/* font-size.scss */

.fs-sm {
  font-size: $font-size-small !important;
}

.fs-md {
  font-size: $font-size-medium !important;
}

/* … and so on */

/* font-weight.scss */

.fw-reg {
  font-weight: $font-weight-regular !important;
}

.fw-semi {
  font-weight: $font-weight-semibold !important;
}

/* … and so on */

/* font-color.scss */

.fc-base {
  color: $font-color-base !important;
}

.fc-accent {
  color: $font-color-accent !important;
}

/* … and so on */

These composable utilities are much easier to use than creating a specific, named component for every typographic variation, and will reduce stylesheet bloat over time while still affording you the flexibility to modulate your typography while adhering to your style guide.

I sometimes prefix utility classes with u- in order to destinguish them as utilities within a codebase that is otherwise following a different naming convention. This helps myself and others avoid confusing them for components.

So there you have it – a few highly useful utility classes that you can start using today alongside your favorite flavor of object-oriented CSS.