Front-end Engineering Lab
PatternsDesign Systems

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

  1. Consistent Base: 4px or 8px base unit
  2. Scale Adherence: Use only scale values
  3. Semantic Naming: Map usage to values
  4. Mobile-First: Start tight, expand for larger screens
  5. Optical Adjustments: Trust your eye over math
  6. Touch Targets: 44px minimum
  7. Vertical Rhythm: Align to baseline grid
  8. Component Spacing: Internal component spacing consistent
  9. Layout Spacing: Larger gaps between sections
  10. 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!

On this page