Spacing Scale Design: T-shirt Sizes vs Fibonacci vs 8pt Grid
T-shirt sizes, Fibonacci, or the 8pt grid — which spacing scale actually ships better UIs? A developer's breakdown with real Tailwind and CSS examples.
Which Spacing Scale Should You Actually Use?
Honestly, most teams pick a spacing scale by accident — they inherit whatever the framework defaulted to, ship it, and never revisit it. That's not a tragedy by itself, but it does explain why so many design systems fall apart the moment a second designer joins the team.
There are three dominant philosophies in the wild right now: T-shirt sizing (xs/sm/md/lg/xl), Fibonacci-derived sequences (0, 1, 2, 3, 5, 8, 13, 21...), and the 8pt grid (multiples of 8: 8, 16, 24, 32...). Each has genuine trade-offs. None is universally correct. What matters is consistency, documentation, and team buy-in.
This article walks through all three — how they work mathematically, where they break down, and how to implement whichever you pick as CSS custom properties that actually survive a design handoff. We'll use Tailwind v4.0.2 examples throughout since that's where most React projects live in 2026.
T-shirt Size Spacing: Intuitive but Dangerously Vague
T-shirt sizing (xs through 2xl or beyond) feels natural to humans because it maps to language we already use. A designer can say 'give that card a large padding' and everyone on the call nods. That shared vocabulary is real and worth something.
The problem surfaces when you try to define what 'large' actually means in pixels. Is lg 16px? 24px? 32px? Every team answers differently, and without a written spec, the answer drifts over time. You'll end up with a Figma file where 'spacing-lg' is 20px and a codebase where it's 24px, and both will be called correct by different people.
T-shirt scales also struggle at the extremes. Once you've got xs, sm, md, lg, xl, 2xl, someone always needs 3xl, then 4xl, then a one-off 100px margin for a hero section. The scale stops being a system and starts being a list. If you're going this route, pin every token to an explicit pixel value in a single source of truth — a spacing system in CSS is the safest way to do that.
Fibonacci Spacing: Aesthetically Satisfying, Practically Messy
The Fibonacci sequence (1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89...) shows up everywhere in nature, and designers love invoking it as justification for spacing choices. The rationale is that proportional growth mirrors how humans perceive visual distance — small gaps feel proportionally smaller next to large ones.
In practice, a pure Fibonacci scale in pixels is awkward. 13px and 21px don't align cleanly to device pixels on most screens. You end up with anti-aliasing artifacts on non-retina displays or weird sub-pixel rendering in certain browsers. The beauty of the math doesn't survive contact with a 1080p monitor.
That said, a modified Fibonacci-inspired scale is genuinely useful. Rounding to the nearest 4px or 8px — so you get 4, 8, 12, 20, 32, 52, 84 — gives you the proportional feel without the pixel chaos. It's worth experimenting with if you're building something visually expressive, like an editorial layout or a marketing site. For component libraries, though, it's usually more friction than it's worth.
The 8pt Grid: Boring, Battle-Tested, Correct
The 8pt grid is exactly what it sounds like: every spacing value is a multiple of 8. So your scale is 0, 8, 16, 24, 32, 40, 48, 56, 64... and you stop there unless you have a very good reason. It's boring. It also works.
Why 8? Most device screens divide evenly by 8 — 360px, 375px, 390px, 1280px, 1440px. An 8px base means your layout math almost never produces fractional pixels. Components snap to a predictable rhythm. Designers and developers can communicate with a single number ('that's a 24px gap') and both sides know exactly what that means in Figma and in the browser.
Tailwind v4.0.2 uses a 4px base unit by default (space-1 = 4px, space-2 = 8px, space-4 = 16px). That's close to 8pt but not identical — space-3 gives you 12px, which breaks strict 8pt purity. If you're committed to an 8pt system, you'll want to configure a custom spacing scale. Here's what that looks like:
Implementing an 8pt Grid as CSS Custom Properties
Whether you're using Tailwind, vanilla CSS, or a component library like Empire UI, the right place to define your spacing scale is CSS custom properties. This keeps tokens framework-agnostic and makes design-to-code handoff much cleaner when you're going from Figma to React.
Here's a full 8pt-aligned token set you can drop into any project:
:root {
/* 8pt spacing scale */
--space-0: 0px;
--space-1: 8px;
--space-2: 16px;
--space-3: 24px;
--space-4: 32px;
--space-5: 40px;
--space-6: 48px;
--space-8: 64px;
--space-10: 80px;
--space-12: 96px;
--space-16: 128px;
/* 4pt half-steps for fine-grain control */
--space-half: 4px;
--space-1-5: 12px;
--space-2-5: 20px;
}
/* Example component usage */
.card {
padding: var(--space-3); /* 24px */
gap: var(--space-2); /* 16px */
border-radius: var(--space-1); /* 8px */
margin-bottom: var(--space-4); /* 32px */
}The half-steps (4px, 12px, 20px) are important. You'll need 12px for icon-to-label gaps and small badge paddings. Pretending you'll never need them means you end up with magic numbers anyway — better to define them explicitly upfront.
Mapping Your Scale to Tailwind v4 Config
If your team is on Tailwind v4.0.2, you can wire your custom scale through the @theme directive introduced in v4. This replaces the old tailwind.config.js spacing extension pattern and co-locates your tokens with your CSS.
@import "tailwindcss";
@theme {
--spacing-0: 0px;
--spacing-1: 8px;
--spacing-2: 16px;
--spacing-3: 24px;
--spacing-4: 32px;
--spacing-5: 40px;
--spacing-6: 48px;
--spacing-8: 64px;
--spacing-10: 80px;
--spacing-12: 96px;
/* half-steps */
--spacing-0-5: 4px;
--spacing-1-5: 12px;
--spacing-2-5: 20px;
}With this in place, p-3 gives you 24px, gap-2 gives you 16px, and the whole team is working from one ground truth. No more mental math to convert Tailwind steps back to pixels. Pair this with your color system design tokens and you've got a real design system foundation.
One thing to watch: Tailwind v4 still generates utility classes for all spacing values, so a very large custom scale will bloat your CSS output even with tree-shaking. Stick to roughly 12-15 values and add more only when you genuinely need them.
When Your Spacing Scale Breaks (and How to Fix It)
Every spacing system breaks somewhere. The 8pt grid breaks on dense data tables — you don't want 8px between table rows, you want 4px or 6px. Fibonacci breaks when you need to align components designed in Figma by someone who was not thinking about Fibonacci at all. T-shirt sizes break when a new hire asks what 'xl' means and nobody can give the same answer twice.
The fix in all three cases is the same: document exceptions explicitly, don't let them bleed back into the base scale. A --space-table-row: 5px token is fine. Adding 5px to your main spacing scale is not.
What should you do when you're inheriting a broken spacing system? Audit first. Run a quick grep across your component files for hard-coded pixel values outside your token set. Then map each one to the nearest token and file a ticket for anything that doesn't fit. Don't try to fix everything at once — pick the highest-traffic components and normalize those. The rest can wait. Your Storybook component library is a good place to surface these inconsistencies visually before you commit to a migration.
Choosing the Right System for Your Project Type
So which do you actually pick? Here's an honest breakdown. If you're building a B2B SaaS product with dense information architecture — dashboards, data grids, sidebars — go 8pt. Full stop. The predictability pays dividends every sprint.
If you're building a marketing site or editorial product where breathing room and visual rhythm matter more than information density, a Fibonacci-inspired scale (rounded to 4pt steps) gives you more expressive range. The jumps between values feel more intentional.
T-shirt sizing works fine as a naming layer on top of either of the other systems. Call your 8px value 'sm', your 16px value 'md', your 24px value 'lg' — that's perfectly valid. Just make sure the underlying pixel values are locked down somewhere. The naming is UX for your team; the pixel values are UX for your users. Both matter. And remember that whatever you choose will affect theme toggle implementations in React too — dark mode spacing rarely changes, but token naming absolutely needs to be consistent across themes.
Does your team actually need all three levels — base unit, scale, and semantic aliases? Probably not at first. Start with the scale, add semantic aliases when you've shipped enough components to see patterns emerge. Over-engineering your token system before you have real usage data is how you end up with 200 spacing tokens and a team that ignores all of them.
FAQ
A 4pt grid uses multiples of 4px as your base unit (4, 8, 12, 16, 20...), while an 8pt grid uses multiples of 8px (8, 16, 24, 32...). Tailwind's default spacing scale is 4pt-based. The 8pt grid is stricter and produces fewer spacing options, which forces more consistency. Most teams on 8pt also allow 4px as a half-step for tight UI elements like icon gaps and badge padding.
Yes. You can define a custom spacing scale in Tailwind v4.0.2 using the @theme directive. Set your values to rounded Fibonacci numbers — something like 4, 8, 12, 20, 32, 52, 84 — mapped to --spacing-1 through --spacing-7. Tailwind doesn't care what the pixel values are, it just generates utilities from whatever you provide.
Rems are generally safer for accessibility. A user who sets their browser default font size to 20px will get proportionally larger spacing with rem-based tokens, which often improves usability for that user. That said, pixel values are easier to reason about during development. A common pattern is to define base values in px and convert to rems for your token output: 16px becomes 1rem, 24px becomes 1.5rem, and so on.
Typically 10-15 is enough for most products. More than that and developers start picking values arbitrarily rather than systematically. A good minimum set for an 8pt grid: 0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96. Add semantic aliases (--space-component-padding, --space-section-gap) on top once you see which raw values keep showing up in the same contexts.
Agree on one source of truth first — usually a shared token file or a Figma Variables library. Then run an audit on both sides: extract all spacing values from Figma components and from the codebase, identify where they diverge, and map them to a unified scale. The important thing is not doing this simultaneously across the whole product — pick two or three core components, align those, and use them as the reference for everything else.
Empire UI is built on top of Tailwind CSS and inherits its 4pt-based spacing scale by default. Since the library supports 40 visual styles, spacing tokens can be overridden per theme using CSS custom properties. For teams adopting a strict 8pt grid, you can extend the Tailwind config or @theme block to remap the spacing values without touching any component source code.