How to Build a Spacing System That Keeps Layouts Consistent

A whiteboard covered in sketched layout diagrams and spacing notes

Nothing is technically broken, and the product still looks a little off. Buttons sit too close to the text above them on one screen and too far on another. A card has 24 pixels of padding on the left and 20 on the right, nobody remembers deciding that, and it's been shipped for three months. None of this triggers a bug report. It just makes the whole thing feel slightly unfinished in a way that's hard to point at directly, and the cause is almost always the same thing: there's no spacing system, just a long accumulation of individual decisions.

Color and typography get design systems early, because they're visible and easy to name. Spacing usually doesn't, because a single inconsistent margin is invisible in isolation. It only becomes obvious at scale, once dozens of screens each made their own slightly different call, and by then it's a much bigger job to fix than it would have been to prevent.

Why spacing drifts faster than color or type

Every new component needs some amount of padding and margin, and in the absence of a rule, each engineer or designer picks a number that looks right on their screen, at their zoom level, for their specific content. Multiply that by a year of feature development across a team of any size, and you get a layout built from dozens of locally reasonable but globally inconsistent decisions.

Color and type values are usually limited to a short list (a handful of brand colors, two or three font sizes per context), so even an undisciplined team tends to reuse the same few values out of habit. Spacing has no natural limit. Padding can be 4px or 6px or 10px or 14px and all of them look fine on their own screen. There's nothing stopping the number from drifting, which is exactly why it needs an explicit system more than color does, not less.

The base unit that makes everything else easy

Most spacing systems start from a base unit, most commonly 4px or 8px, and build every spacing value as a multiple of it. An 8px base gives you 8, 16, 24, 32, 48, 64, and so on. This single decision does more work than almost anything else in the system: it means every spacing value anyone chooses is automatically compatible with every other one, because they all divide cleanly into the same unit.

a ruler laid across a blueprint grid measuring out spacing
Photo by Jef K on Pexels

The choice between 4px and 8px mostly comes down to how much granularity the product actually needs. An 8px base is coarser and easier to hold in your head; a 4px base gives finer control for dense interfaces like data tables or admin panels where an 8px jump is too big a step between adjacent elements. Pick one, not both, and resist the urge to special-case a component into an off-grid value just because it looked slightly better that one time.

Naming the scale instead of using raw pixel values

A spacing scale is more useful as named tokens than as raw numbers scattered through code: space-xs, space-sm, space-md, space-lg, space-xl mapped to the multiples of your base unit. This matters because raw pixel values invite exceptions ("just this once, 14px instead of 16px") in a way that named tokens discourage, since reaching for space-md is a smaller cognitive step than typing an arbitrary number.

Named tokens also make a future rebrand or density change trivial. If the whole product needs to feel slightly more spacious, you change what space-md resolves to once, and every component built on the token picks up the change automatically. A product built on raw pixel values has no single point of control, just hundreds of places that each need to be found and edited individually.

Deciding what each spacing size is actually for

A scale without usage rules just moves the inconsistency problem one level up: now people have five or six values to choose from instead of infinite ones, but nothing tells them which to use where. Pair the scale with a short, specific usage guide: space-xs for spacing between a label and its input, space-sm for spacing between related elements inside a single component, space-md for spacing between distinct components, space-lg and up for section-level separation on a page.

an architect's blueprint with a pencil and ruler resting on top
Photo by Thirdman on Pexels

This is the part that actually prevents drift day to day. Without it, a new component still ends up with an arbitrary choice between five options instead of an arbitrary choice between infinite ones, which is better but not solved. The usage guide is what turns a scale into a system.

Component-level spacing versus layout-level spacing

It helps to separate spacing into two different jobs that often get conflated: spacing inside a component (padding around a button's label, gap between an icon and text) and spacing between components on a page (margin between a header and the content below it, gutter between cards in a grid). These two jobs can use the same base scale, but they tend to cluster around different ends of it, and treating them as one undifferentiated pile of "spacing" is where a lot of inconsistency creeps back in.

Internal component spacing tends to stay small and tight, usually the bottom two or three steps of the scale, because it's about relationships between tightly coupled elements. Layout-level spacing between distinct sections tends to live at the top of the scale, because it needs to visually separate things that aren't part of the same unit. Mixing the two, using a layout-sized gap inside a component or a component-sized gap between page sections, is a common and specific way spacing starts to feel wrong without being obviously wrong.

Responsive spacing without a separate scale for every breakpoint

Spacing often needs to shrink somewhat on smaller screens without the relationships between elements changing, which tempts teams into building a parallel spacing scale per breakpoint. That's more maintenance than the problem usually requires. A more durable approach is to keep one scale and let a small number of tokens (usually just the layout-level ones) respond to viewport size through CSS clamp() or a handful of media query overrides, while component-internal spacing stays fixed across breakpoints since it rarely needs to change with screen size.

This keeps the system small enough that people can actually remember it. A spacing system with one scale and a few responsive overrides is something a new team member can learn in an afternoon. A spacing system with a different scale per breakpoint is something nobody fully remembers, which defeats the purpose of having a system at all.

Enforcing the system once it exists

A documented scale that nobody enforces decays the same way an undocumented one does, just more slowly. Linting rules that flag raw pixel values in CSS or component styles, paired with a design tool library that only exposes the approved spacing tokens as options, close off the two easiest ways drift gets back in: a developer reaching for an arbitrary number because the deadline is close, or a designer nudging a frame in Figma by a few pixels because it "looked better."

"The spacing system that survives is the one that's slightly annoying to break. If going off-scale is just as easy as staying on it, someone will go off-scale by Friday afternoon, not because they don't care, but because nothing stopped them in the moment." - Dennis Traina, founder of 137Foundry

Neither enforcement mechanism needs to be aggressive. The goal is friction, not a hard block, since there are occasionally legitimate one-off cases. The point is that the exception should require a deliberate decision, not just be the path of least resistance.

Auditing an existing product's drift before fixing it

Before changing anything, it's worth measuring how much drift already exists. Pull computed padding and margin values across a representative sample of screens using browser DevTools or a simple script against the rendered DOM, and you'll usually find a long tail of near-duplicate values clustered around a few intended numbers: a dozen different paddings that are all "basically 16px" but not quite. That cluster is the signal that a scale was never formalized, just approximated independently many times over.

This audit also tells you which values to actually standardize on. The scale should reflect what the product is already mostly doing, adjusted to the nearest clean multiple of your base unit, rather than an arbitrary scale imported from a different product that doesn't match your existing visual rhythm.

Where to start if the product already has this problem

Retrofitting a spacing system onto an existing product is a bigger job than building one from the start, but it doesn't have to happen all at once. Start with the components used most frequently (buttons, form fields, cards) since fixing those has the highest visible impact for the least effort, and expand outward from there as new work touches other parts of the interface. A spacing system that covers 70 percent of the product consistently beats a plan to cover 100 percent that never gets finished.

For teams building this out, 137Foundry's web design team has worked through this exact retrofit on products that grew past the point where ad hoc spacing still worked, and the services page covers the broader design system work that spacing systems usually get built alongside. 137Foundry takes on both net-new design system builds and retrofits on products that are already shipping.

Sources and further reading: Tailwind CSS documents one widely used implementation of a scaled spacing system, Material Design covers Google's approach to the same problem at a larger scale, MDN has the full CSS reference for clamp() and custom properties used to implement a scale, and the Nielsen Norman Group publishes general UX research on how visual consistency affects perceived product quality.

Need help with Web Development?

137Foundry builds custom software, AI integrations, and automation systems for businesses that need real solutions.

Book a Free Consultation View Services