Designing a Reusable Scale System

Table of Contents

Summary Return to table of contents

I was brought on to stand up a design system for a PBM product.

Prior to my arrival a small amount of work had been done towards this goal, but nothing had been fully documented, implemented, or adopted internally. After an extensive audit of the existing UI, potential systems work, and gathering an understanding of how the 6+ internal development teams worked, I got to work planning the design system.

This is the story of one segment of the Design System I built, a very foundational and abstract part of system. Scale.

Outcomes after design and development:

What’d you do? Return to table of contents

I was the project lead, systems designer, token author, and developer liaison.

Artifacts of note Return to table of contents

For the scale system I provided:

More detail Return to table of contents

Building design systems is deeply abstracted work, and often requires cross-functional coordination and advocation for adoption.

In the case of Beaker, I had a nearly clean slate to start from, as none of the internal development teams were sharing design or code. This meant I was evaluating disparate approaches and determining and/or defining go-forward approaches to be shared across teams and products.

After extensive audits, I worked on several foundational systems within Beaker.

One of those was Scale.

Scale is a very interesting, often overlooked and misunderstood part of Design System work. It’s hard to define and even harder to visualize for examples and documentation. However scale is a foundation off which everything else is built.


Quick primer: Scale is the prescribed number system used to consistently define all sizing, spacing, layering, and depth considerations. i.e. how large text is, how far apart elements are, how much space is put around something, how wide or tall something can or should be, shadow blur and depth, etc…


Scale can be indirectly adopted or directly input. In either case it is always following it’s predetermined course.

The base scale for Beaker was defined using a token : value paradigm.

The token uses a naming convention of prefixed namespace --scale- followed by a 3 digit semantic value (e.g. 004, 008, 016…). This convention allows for modifying the scale without major refactoring—something like t-shirt sizing (e.g. s, m, l, xl…) might cause.

Below is the delivered base scale used in Beaker.

:root{
    /*===
        base scale, multiples of 4
        used for spacing and sizing
        REM values calculated on 100%/16px root
    */
    --scale-004: 0.25rem;
    --scale-008: 0.5rem;
    --scale-012: 0.75rem;
    --scale-016: 1rem;
    --scale-020: 1.25rem;
    --scale-024: 1.5rem;
    --scale-032: 2rem;
    --scale-040: 2.5rem;
    --scale-048: 3rem;
    --scale-056: 3.5rem;
    --scale-064: 4rem;
    --scale-072: 4.5rem;
    --scale-096: 6rem;
    --scale-120: 7.5rem;
}
css

The scale as delivered doesn’t include every 4px increment between 4 and 120, instead we used curated jumps to eliminate gaps that are perceptibly similar.

Additionally as the scale gets larger, the space between each token gets larger as well. From 024–072 the gap is 8, from 072–120 it jumps to 24.

Another interesting capability of using a token structure is the values can be mutable across device types, while the name stays constant.

For example: By definition the --scale-004 token should always have a value roughly equivalent to 4px, however in some cases that value might require a different unit and/or numeric declaration.

:root{
    /*===
        web and browser based
    */
    --scale-004: 0.25rem; /* ~4px */
    ...
}
:root{
    /*===
        iOS
    */
    --scale-004: 3pt; /* ~4px */
    ...
}
:root{
    /*===
        Android, medium density
    */
    --scale-004: 4.00dp; /* ~4px */
    ...
}
css

The approach Return to table of contents

Creating a prescribed linear key/value set has an inherent issue with scalability.

Thinking about how we often approached naming structures early in design systems with t-shirt sizing.

-sm -med -lrg -xl

The opportunity and space for growing the system is small and nonsensical, which is why we started seeing things like -xsm, -xxl, -medsm etc.


For this scale system I proposed a more literal token naming structure.

Defining a probable end point using a 3 digit scale, in digital design we don’t often go above 120px in size for text or spacing. And it’s extremely unlikely to ever go beyond 999 into a 4 digit numbers.

This gives us a clean template for naming. --scale-[size] A size like 4px translates to -004, 24 to -024, and 120 to -120

Since we’ve used the actual number we can fit new items in between, beyond, and before.

Even if the new values don’t fit the set mathematical scale, for instance 18px is a very commonly used size for text, and it’s not part of the base 4 scale. However we can still add it as an exception and not break our naming convention.

--scale-018: 1.125rem and it fits very nicely between --scale-016 and --scale-020


The beauty of this approach is that you can still use t-shirt sizes for your more specific tokens. Scale is the root value of sizing to inject consistency and rhythm to our designs it applies to all sorts of things: font-size, line-height, margin, padding, width… and so on.

You can layer your tokens, you can have something like:

--font-size-sm: var(--scale-014);

It’s far less likely that your typography or your spacing needs will grow much, so you can usually get away with sm, med, lrg here.

And -014 can still be used for spacing too, like:

--button-padding: var(--scale-008) var(--scale-014);

-014 is always the same, but it’s being used flexibly, without defining it in all the places it’s needed. And still padding and font-sizes are always consistent across pages, sections, and apps.

If you want to change the padding on the button, but not the size of your small fonts you can swap the scale token in either case without affecting the other.

--button-padding: var(--scale-008) var(--scale-016);

Buttons change, but stay consistently padded through-out the project and the value of -014 never changes so you don’t have a cascading mess to clean up.

Another cool affect of this structure is your alias tokens can be semantic, and when someone is trying to determine the output of the root token it gives them that answer quickly and without guesswork.

In other words you don’t have to dig around in the code to know this --font-size-sm: var(--scale-014); means “our small font size is ~14 pixels”.

Looking back? Return to table of contents

The original proposal works very well in practice, but as I always say everything can be improved.

I’ve tweaked the scale in a small but impactful way. I removed --scale-040, and replaced it with --scale-036 and --scale-044

I found the jump from 32 to 40 then to 48 too large for most applications of the mid-range scale. -036 and -044 give additional flexibility without jarring visual differences resulting in a smoother reflows in responsive applications.

In retrospect:


Another change is how we handle exceptions in the code. I gave the example of --scale-018 as being common in typography, which remains correct. I originally proposed placing it inline with the rest of the scale even though it doesn’t meet the requirement of multiples of four.

For clarity I recommend placing these exceptions outside the scale and leaving a comment to explain its inclusion.

:root{
    /*===
        base scale, multiples of 4
        used for spacing and sizing
        REM values calculated on 100%/16px root
    */
    --scale-004: 0.25rem;
    --scale-008: 0.5rem;
    --scale-012: 0.75rem;
    ...
    /*===
        scale exceptions!
    */
    --scale-018: 1.125rem; /* exception for nav/code only */
}
css

Giving designers and developers the ability to add outside the defined scale, and the team gets additional clarity on why the exception exists. Encouraging consideration and conversation before adding exceptions.

This change does not apply to extending the scale with multiples of 4. Say we add -040 back, it would go between -036 and -044 as expected.

In retrospect: