Spacing System
Build consistent spacing and layout with a mathematical scale
Spacing System
A spacing system creates visual rhythm and consistency. Instead of arbitrary spacing values, use a mathematical scale that ensures harmony across your UI.
Base Unit
Choose a base unit (usually 4px or 8px):
4px base: 4, 8, 12, 16, 20, 24, 28, 32...
8px base: 8, 16, 24, 32, 40, 48, 56, 64...Recommendation: 4px base for flexibility, 8px increments for most spacing.
Spacing Scale
// tokens/spacing.ts
export const spacing = {
// Numeric scale (4px base)
0: '0',
1: '0.25rem', // 4px
2: '0.5rem', // 8px
3: '0.75rem', // 12px
4: '1rem', // 16px
5: '1.25rem', // 20px
6: '1.5rem', // 24px
7: '1.75rem', // 28px
8: '2rem', // 32px
10: '2.5rem', // 40px
12: '3rem', // 48px
14: '3.5rem', // 56px
16: '4rem', // 64px
20: '5rem', // 80px
24: '6rem', // 96px
32: '8rem', // 128px
40: '10rem', // 160px
48: '12rem', // 192px
56: '14rem', // 224px
64: '16rem', // 256px
// T-shirt sizes (semantic)
xs: '0.25rem', // 4px
sm: '0.5rem', // 8px
md: '1rem', // 16px
lg: '1.5rem', // 24px
xl: '2rem', // 32px
'2xl': '2.5rem', // 40px
'3xl': '3rem', // 48px
'4xl': '4rem', // 64px
'5xl': '5rem', // 80px
};Semantic Spacing
Map usage to scale values:
export const semanticSpacing = {
// Component spacing
component: {
gutter: spacing[4], // 16px - Internal component spacing
gap: spacing[2], // 8px - Gap between items
padding: spacing[4], // 16px - Component padding
},
// Layout spacing
layout: {
sectionGap: spacing[16], // 64px - Between sections
containerGutter: spacing[6], // 24px - Container side padding
gridGap: spacing[6], // 24px - Grid gaps
},
// Element spacing
element: {
iconGap: spacing[2], // 8px - Gap next to icon
labelGap: spacing[1], // 4px - Label to input
stackGap: spacing[4], // 16px - Stack items
},
};CSS Implementation
/* global.css */
:root {
--spacing-0: 0;
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-3: 0.75rem;
--spacing-4: 1rem;
--spacing-6: 1.5rem;
--spacing-8: 2rem;
--spacing-12: 3rem;
--spacing-16: 4rem;
/* Semantic */
--spacing-component-gutter: var(--spacing-4);
--spacing-section-gap: var(--spacing-16);
}
/* Use in components */
.card {
padding: var(--spacing-component-gutter);
gap: var(--spacing-2);
}
.section {
margin-bottom: var(--spacing-section-gap);
}Tailwind Configuration
// tailwind.config.js
import { spacing } from './tokens/spacing';
export default {
theme: {
spacing: spacing,
},
};// Usage
<div className="p-4 gap-2 mb-16">
<Card className="p-6 space-y-4">
<div className="flex items-center gap-2">
<Icon />
<Text>Label</Text>
</div>
</Card>
</div>Common Spacing Patterns
Stack Layout (Vertical)
// Consistent vertical spacing
<div className="space-y-4">
<Card />
<Card />
<Card />
</div>
// Or with Stack component
<Stack gap={4}>
<Card />
<Card />
<Card />
</Stack>Inline Layout (Horizontal)
// Consistent horizontal spacing
<div className="flex gap-2">
<Button />
<Button />
<Button />
</div>Grid Layout
// Consistent grid gaps
<div className="grid grid-cols-3 gap-6">
<Card />
<Card />
<Card />
</div>Section Spacing
// Large gaps between sections
<main className="space-y-16">
<section>Hero</section>
<section>Features</section>
<section>Testimonials</section>
</main>Spacing Components
Stack
// components/Stack.tsx
interface StackProps {
gap?: keyof typeof spacing;
direction?: 'vertical' | 'horizontal';
children: React.ReactNode;
}
export function Stack({ gap = 4, direction = 'vertical', children }: StackProps) {
return (
<div
className={direction === 'vertical' ? 'flex flex-col' : 'flex'}
style={{ gap: spacing[gap] }}
>
{children}
</div>
);
}
// Usage
<Stack gap={4}>
<Card />
<Card />
</Stack>Spacer
// components/Spacer.tsx
interface SpacerProps {
size?: keyof typeof spacing;
axis?: 'horizontal' | 'vertical';
}
export function Spacer({ size = 4, axis = 'vertical' }: SpacerProps) {
return (
<div
style={{
[axis === 'vertical' ? 'height' : 'width']: spacing[size],
}}
/>
);
}
// Usage
<div>
<Content />
<Spacer size={8} />
<Content />
</div>Container
// components/Container.tsx
interface ContainerProps {
padding?: keyof typeof spacing;
maxWidth?: string;
children: React.ReactNode;
}
export function Container({
padding = 6,
maxWidth = '1280px',
children
}: ContainerProps) {
return (
<div
style={{
maxWidth,
paddingLeft: spacing[padding],
paddingRight: spacing[padding],
marginLeft: 'auto',
marginRight: 'auto',
}}
>
{children}
</div>
);
}Responsive Spacing
// Responsive spacing tokens
export const responsiveSpacing = {
mobile: {
gutter: spacing[4], // 16px
sectionGap: spacing[8], // 32px
},
tablet: {
gutter: spacing[6], // 24px
sectionGap: spacing[12], // 48px
},
desktop: {
gutter: spacing[8], // 32px
sectionGap: spacing[16], // 64px
},
};/* Mobile-first responsive spacing */
.section {
padding: 1rem;
margin-bottom: 2rem;
}
@media (min-width: 768px) {
.section {
padding: 1.5rem;
margin-bottom: 3rem;
}
}
@media (min-width: 1024px) {
.section {
padding: 2rem;
margin-bottom: 4rem;
}
}Negative Spacing
// For overlapping elements
export const negativeSpacing = {
'-1': '-0.25rem',
'-2': '-0.5rem',
'-4': '-1rem',
'-6': '-1.5rem',
'-8': '-2rem',
};// Overlap elements
<div className="relative">
<Avatar />
<Badge className="absolute -top-2 -right-2" />
</div>Touch Targets
// Minimum touch target: 44x44px (iOS) or 48x48px (Material)
export const touchTargets = {
minimum: '44px',
comfortable: '48px',
large: '56px',
};/* Ensure adequate touch targets */
.button {
min-height: 44px;
min-width: 44px;
padding: var(--spacing-2) var(--spacing-4);
}Optical Adjustments
Sometimes mathematically correct spacing looks wrong. Make optical adjustments:
/* Icon and text: reduce gap slightly */
.icon-text {
gap: 6px; /* Not 8px - looks more balanced */
}
/* Circular elements: appear smaller, add more space */
.avatar {
margin-right: 12px; /* Instead of 8px */
}Grid Systems
// 12-column grid
export const grid = {
columns: 12,
gutter: spacing[6], // 24px
margin: spacing[4], // 16px
// Container widths
maxWidths: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
},
};Vertical Rhythm
Maintain consistent vertical rhythm with line-height and spacing:
/* Base line height: 1.5 (24px on 16px font) */
body {
line-height: 1.5;
}
/* Spacing in multiples of line height */
h1 {
margin-top: 3rem; /* 2x line height */
margin-bottom: 1.5rem; /* 1x line height */
}
p {
margin-bottom: 1.5rem; /* 1x line height */
}Best Practices
- Consistent Base: 4px or 8px base unit
- Scale Adherence: Use only scale values
- Semantic Naming: Map usage to values
- Mobile-First: Start tight, expand for larger screens
- Optical Adjustments: Trust your eye over math
- Touch Targets: 44px minimum
- Vertical Rhythm: Align to baseline grid
- Component Spacing: Internal component spacing consistent
- Layout Spacing: Larger gaps between sections
- Negative Space: Use whitespace generously
Common Pitfalls
❌ Arbitrary values: 15px, 23px, 37px
✅ Use spacing scale
❌ Inconsistent gaps: Different everywhere
✅ Semantic spacing tokens
❌ Cramped mobile: Not enough space
✅ Adequate breathing room
❌ Small touch targets: 32px buttons
✅ 44px minimum
❌ Too tight: Everything jammed together
✅ Generous whitespace
A consistent spacing system creates visual harmony and makes your UI feel polished and professional!