← Back to blog

CSS/SCSS Utility Helpers: Build Your Own or Pick a Library

You’ve written padding: 1rem a thousand times. You’ve created .flex-container classes that do nothing but set display: flex. At some point, every CSS codebase drifts toward the same pattern — small, single-purpose classes that map directly to CSS properties. That’s the utility-first idea. And you can build it yourself in about 50 lines of SCSS.

What Utility Classes Look Like

You already know these even if you’ve never used a utility framework:

<div class="d-flex p-4 m-2 text-center font-bold text-primary">
  Content here
</div>

Each class does one thing. .p-4 adds padding. .d-flex sets display to flex. .text-primary sets the brand color. No specificity wars. No hunting through stylesheets to find what’s overriding what.

The power is in the consistency. You pick a spacing scale once, generate the classes, and your entire team uses the same values everywhere.

Building a Spacing Utility Generator

Here’s where SCSS gets fun. Define your spacing scale as a map, then loop over it.

$spacers: (
  0: 0,
  1: 0.25rem,
  2: 0.5rem,
  3: 0.75rem,
  4: 1rem,
  5: 1.25rem,
  6: 1.5rem,
  7: 2rem,
  8: 3rem,
);

@each $key, $value in $spacers {
  .p-#{$key} { padding: $value; }
  .pt-#{$key} { padding-top: $value; }
  .pb-#{$key} { padding-bottom: $value; }
  .pl-#{$key} { padding-left: $value; }
  .pr-#{$key} { padding-right: $value; }
  .px-#{$key} { padding-left: $value; padding-right: $value; }
  .py-#{$key} { padding-top: $value; padding-bottom: $value; }

  .m-#{$key} { margin: $value; }
  .mt-#{$key} { margin-top: $value; }
  .mb-#{$key} { margin-bottom: $value; }
  .ml-#{$key} { margin-left: $value; }
  .mr-#{$key} { margin-right: $value; }
  .mx-#{$key} { margin-left: $value; margin-right: $value; }
  .my-#{$key} { margin-top: $value; margin-bottom: $value; }
}

That loop generates 135 classes from 9 scale values. .p-0 through .p-8, .mt-0 through .mt-8, and every direction in between. One map to change, and every spacing utility updates.

Color Utilities

Same pattern. Define a color map, loop through it.

$colors: (
  primary: #2563eb,
  secondary: #64748b,
  success: #16a34a,
  danger: #dc2626,
  warning: #d97706,
  light: #f8fafc,
  dark: #0f172a,
);

@each $name, $color in $colors {
  .text-#{$name} { color: $color; }
  .bg-#{$name} { background-color: $color; }
  .border-#{$name} { border-color: $color; }
}

Now you have .text-primary, .bg-danger, .border-success — all from seven lines of loop logic.

Display and Typography Utilities

For properties with a fixed set of values, a map of property-value pairs works well.

$displays: (none, block, inline, inline-block, flex, inline-flex, grid);

@each $value in $displays {
  .d-#{$value} { display: $value; }
}

$font-weights: (
  light: 300,
  normal: 400,
  medium: 500,
  semibold: 600,
  bold: 700,
);

@each $name, $weight in $font-weights {
  .font-#{$name} { font-weight: $weight; }
}

$text-aligns: (left, center, right, justify);

@each $value in $text-aligns {
  .text-#{$value} { text-align: $value; }
}
Without utilities
You write .card-wrapper-flex-centered, .sidebar-padded-large, .header-text-bold — dozens of one-off classes that mean nothing outside their context.
With utilities
You write d-flex p-4 font-bold — classes that are immediately obvious, reusable everywhere, and come from a single source of truth.

Making It Responsive

Real utility libraries generate responsive variants. You can do that too.

$breakpoints: (
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px,
);

@each $bp-name, $bp-value in $breakpoints {
  @media (min-width: $bp-value) {
    @each $key, $value in $spacers {
      .#{$bp-name}\:p-#{$key} { padding: $value; }
      .#{$bp-name}\:m-#{$key} { margin: $value; }
    }
    @each $d in $displays {
      .#{$bp-name}\:d-#{$d} { display: $d; }
    }
  }
}

Now you can write sm:d-flex md:p-4 lg:p-8. This is where hand-rolling starts getting heavy, though — and where existing libraries earn their keep.

When to Build Your Own vs Use a Library

Build your own when:

  • You need a handful of spacing and color utilities on a small project
  • Your team has a strict design system and you want full control over the output
  • You’re adding utilities to an existing SCSS codebase
  • Bundle size is critical and you only need 20-30 classes

Use an existing library when:

  • You need responsive variants, states (hover, focus), and dark mode
  • You want documentation your team can reference
  • The project will grow and you don’t want to maintain your own framework
  • You value community tooling (editor plugins, linting, prettier integrations)

The line is roughly this: if you’re generating more than 100 utilities with responsive variants, you’re rebuilding Tailwind poorly. Just use Tailwind.

Public Utility Libraries Compared

Library Approach Size Best for
Tailwind CSS Utility-first, JIT compiled ~10 KB (purged) Greenfield projects, SPAs, any serious UI work
Bootstrap Utilities Component + utility hybrid ~25 KB (utilities only) Projects already using Bootstrap
Tachyons Functional CSS, pre-built ~14 KB (gzipped) Simple sites, prototyping, no build step needed
Open Props CSS custom properties ~5 KB (only what you import) Design tokens without utility classes, progressive enhancement

Tailwind CSS is the default choice in 2026. The JIT compiler means you ship only the classes you actually use. The ecosystem is massive — editor autocomplete, Prettier plugin, component libraries like Headless UI. If you’re starting a new project, this is what I’d pick unless you have a specific reason not to.

Bootstrap utilities make sense if you’re already on Bootstrap. Since v5, Bootstrap ships a utility API that lets you generate custom utilities using the same SCSS map pattern we covered above. Don’t add Bootstrap just for the utilities, though.

Tachyons was ahead of its time. It’s smaller than Tailwind and requires zero configuration. No build step, no purging, just a CSS file. The tradeoff is no JIT, no custom config without forking it, and a smaller community. Good for quick projects where you want utilities without any tooling.

Open Props takes a different path entirely. Instead of utility classes, it gives you CSS custom properties — var(--size-3), var(--blue-7). You write your own classes using these tokens. It’s less opinionated, plays well with any CSS approach, and the bundle is tiny because you only import the prop categories you need.

When Utility Classes Hurt

Utility classes aren’t free. There’s a real cost.

<div class="d-flex items-center justify-between p-4 mx-2 my-4 bg-light
  border-primary rounded-lg text-dark font-semibold text-sm hover:bg-primary
  hover:text-light transition-colors duration-200 sm:p-6 md:flex-row
  lg:p-8 lg:mx-auto lg:max-w-4xl">

That’s hard to read. It’s hard to diff in a PR. And it scatters your styling decisions across every template file instead of centralizing them.

The fix is extraction. When a pattern repeats, pull it into a component or use @apply (in Tailwind) to compose utilities into a semantic class. Utilities are for one-off styling and rapid prototyping. Repeated patterns should become abstractions.

Rule of thumb: If you're copying the same string of 5+ utility classes across three or more elements, extract it. Either into a component, a CSS class with @apply, or a shared style object in your framework.

The Takeaway

Start small. If you need a few spacing and color helpers, write them yourself with SCSS maps and @each. It takes 10 minutes and you own every line.

If you need responsive variants, hover states, dark mode, and a complete design system — use Tailwind. Don’t reinvent that wheel.

Either way, the core idea is the same: define your design tokens once, generate classes from them, and stop writing one-off CSS for the same properties over and over. Your stylesheets get smaller. Your team stays consistent. And you never argue about whether padding should be 14px or 16px again.


Sources: