- **Component Creation Guide:** Document best practices for creating reusable, accessible components using CVA patterns. Includes guidance on when to compose vs create, decision trees, templates, prop design, testing checklists, and real-world examples. - **Design System README:** Introduce an organized structure for the design system documentation with quick navigation, learning paths, and reference links to key topics. Includes paths for quick starts, layouts, components, forms, and AI setup.
16 KiB
Spacing Philosophy
Master the "parent controls children" spacing strategy that eliminates 90% of layout inconsistencies. Learn when to use margin, padding, or gap—and why children should never add their own margins.
Table of Contents
- The Golden Rules
- Parent Controls Children Strategy
- Decision Tree: Margin vs Padding vs Gap
- Common Patterns
- Before/After Examples
- Anti-Patterns to Avoid
- Quick Reference
The Golden Rules
These 5 rules eliminate 90% of spacing inconsistencies:
Rule 1: Parent Controls Children
Children don't add their own margins. The parent controls spacing between siblings.
// ✅ CORRECT - Parent controls spacing
<div className="space-y-4">
<Card>Item 1</Card>
<Card>Item 2</Card>
<Card>Item 3</Card>
</div>
// ❌ WRONG - Children add margins
<div>
<Card className="mb-4">Item 1</Card>
<Card className="mb-4">Item 2</Card>
<Card>Item 3</Card> {/* Inconsistent: last one has no margin */}
</div>
Why this matters:
- Eliminates "last child" edge cases
- Makes components reusable (they work in any context)
- Changes propagate from one place (parent)
- Prevents margin collapsing bugs
Rule 2: Use Gap for Siblings
For flex and grid layouts, use gap-* to space siblings.
// ✅ CORRECT - Gap for flex/grid
<div className="flex gap-4">
<Button>Cancel</Button>
<Button>Save</Button>
</div>
<div className="grid grid-cols-3 gap-6">
<Card>1</Card>
<Card>2</Card>
<Card>3</Card>
</div>
// ❌ WRONG - Children with margins
<div className="flex">
<Button className="mr-4">Cancel</Button>
<Button>Save</Button>
</div>
Rule 3: Use Padding for Internal Spacing
Padding is for spacing inside a component, between the border and content.
// ✅ CORRECT - Padding for internal spacing
<Card className="p-6">
<CardTitle>Title</CardTitle>
<CardContent>Content</CardContent>
</Card>
// ❌ WRONG - Using margin for internal spacing
<Card>
<CardTitle className="m-6">Title</CardTitle>
</Card>
Rule 4: Use space-y for Vertical Stacks
For vertical stacks (not flex/grid), use space-y-* utility.
// ✅ CORRECT - space-y for stacks
<form className="space-y-4">
<Input />
<Input />
<Button />
</form>
// ❌ WRONG - Children with margins
<form>
<Input className="mb-4" />
<Input className="mb-4" />
<Button />
</form>
How space-y works:
/* space-y-4 applies margin-top to all children except first */
.space-y-4 > * + * {
margin-top: 1rem; /* 16px */
}
Rule 5: Margins Only for Exceptions
Use margin only when a specific child needs different spacing from its siblings.
// ✅ CORRECT - Margin for exception
<div className="space-y-4">
<Card>Normal spacing</Card>
<Card>Normal spacing</Card>
<Card className="mt-8">Extra spacing above this one</Card>
<Card>Normal spacing</Card>
</div>
// Use case: Visually group related items
<div className="space-y-4">
<h2>Section 1</h2>
<p>Content</p>
<h2 className="mt-12">Section 2</h2> {/* Extra margin to separate sections */}
<p>Content</p>
</div>
Parent Controls Children Strategy
The Problem with Child-Controlled Spacing
When children control their own margins:
// ❌ ANTI-PATTERN
function TodoItem({ className }: { className?: string }) {
return <div className={cn("mb-4", className)}>Todo</div>;
}
// Usage
<div>
<TodoItem /> {/* Has mb-4 */}
<TodoItem /> {/* Has mb-4 */}
<TodoItem /> {/* Has mb-4 - unwanted margin at bottom! */}
</div>
Problems:
- ❌ Last item has unwanted margin
- ❌ Can't change spacing without modifying component
- ❌ Margin collapsing creates unpredictable spacing
- ❌ Component not reusable in different contexts
The Solution: Parent-Controlled Spacing
// ✅ CORRECT PATTERN
function TodoItem({ className }: { className?: string }) {
return <div className={className}>Todo</div>; // No margin!
}
// Parent controls spacing
<div className="space-y-4">
<TodoItem />
<TodoItem />
<TodoItem /> {/* No unwanted margin */}
</div>
// Different context, different spacing
<div className="space-y-2">
<TodoItem />
<TodoItem />
</div>
// Another context, flex layout
<div className="flex gap-6">
<TodoItem />
<TodoItem />
</div>
Benefits:
- ✅ No edge cases (last child, first child, only child)
- ✅ Spacing controlled in one place
- ✅ Component works in any layout context
- ✅ No margin collapsing surprises
- ✅ Easier to maintain and modify
Decision Tree: Margin vs Padding vs Gap
Use this flowchart to choose the right spacing method:
┌─────────────────────────────────────────────┐
│ What are you spacing? │
└─────────────┬───────────────────────────────┘
│
┌──────┴──────┐
│ │
▼ ▼
Siblings? Inside a component?
│ │
│ └──> USE PADDING
│ className="p-4"
│
├──> Is parent using flex or grid?
│ │
│ ├─YES──> USE GAP
│ │ className="flex gap-4"
│ │ className="grid gap-6"
│ │
│ └─NO───> USE SPACE-Y or SPACE-X
│ className="space-y-4"
│ className="space-x-2"
│
└──> Exception case?
(One child needs different spacing)
│
└──> USE MARGIN
className="mt-8"
Common Patterns
Pattern 1: Form Fields (Vertical Stack)
// ✅ CORRECT
<form className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input id="password" />
</div>
<Button type="submit">Submit</Button>
</form>
Spacing breakdown:
space-y-4on form: 16px between field groupsspace-y-2on field group: 8px between label and input- No margins on children
Pattern 2: Button Group (Horizontal Flex)
// ✅ CORRECT
<div className="flex gap-4">
<Button variant="outline">Cancel</Button>
<Button>Save</Button>
</div>
// Responsive: stack on mobile, row on desktop
<div className="flex flex-col sm:flex-row gap-4">
<Button variant="outline">Cancel</Button>
<Button>Save</Button>
</div>
Why gap over space-x:
- Works with
flex-wrap - Works with
flex-col(changes direction) - Consistent spacing in all directions
Pattern 3: Card Grid
// ✅ CORRECT
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<Card>Item 1</Card>
<Card>Item 2</Card>
<Card>Item 3</Card>
</div>
Why gap:
- Consistent spacing between rows and columns
- Works with responsive grid changes
- No edge cases (first row, last column, etc.)
Pattern 4: Card Internal Spacing
// ✅ CORRECT
<Card className="p-6">
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</CardContent>
<CardFooter className="pt-4">
<Button>Action</Button>
</CardFooter>
</Card>
Spacing breakdown:
p-6on Card: 24px internal paddingspace-y-4on CardContent: 16px between paragraphspt-4on CardFooter: Additional top padding for visual separation
Pattern 5: Page Layout
// ✅ CORRECT
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto space-y-6">
<h1 className="text-3xl font-bold">Page Title</h1>
<Card>
<CardHeader>
<CardTitle>Section 1</CardTitle>
</CardHeader>
<CardContent>Content</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Section 2</CardTitle>
</CardHeader>
<CardContent>Content</CardContent>
</Card>
</div>
</div>
Spacing breakdown:
px-4: Horizontal padding (prevents edge touching)py-8: Vertical padding (top and bottom spacing)space-y-6: 24px between sections- No margins on children
Before/After Examples
Example 1: Button Group
❌ Before (Child-Controlled)
function ActionButton({ children, className }: Props) {
return <Button className={cn("mr-4", className)}>{children}</Button>;
}
// Usage
<div className="flex">
<ActionButton>Cancel</ActionButton>
<ActionButton>Save</ActionButton>
<ActionButton>Delete</ActionButton> {/* Unwanted mr-4 */}
</div>
Problems:
- Last button has unwanted margin
- Can't change spacing without modifying component
- Hard to use in vertical layout
✅ After (Parent-Controlled)
function ActionButton({ children, className }: Props) {
return <Button className={className}>{children}</Button>;
}
// Usage
<div className="flex gap-4">
<ActionButton>Cancel</ActionButton>
<ActionButton>Save</ActionButton>
<ActionButton>Delete</ActionButton>
</div>
// Different context: vertical
<div className="flex flex-col gap-2">
<ActionButton>Cancel</ActionButton>
<ActionButton>Save</ActionButton>
</div>
Benefits:
- No edge cases
- Reusable in any layout
- Easy to change spacing
Example 2: List Items
❌ Before (Child-Controlled)
function ListItem({ title, description }: Props) {
return (
<div className="mb-6 p-4 border rounded">
<h3 className="mb-2">{title}</h3>
<p>{description}</p>
</div>
);
}
<div>
<ListItem title="Item 1" description="..." />
<ListItem title="Item 2" description="..." />
<ListItem title="Item 3" description="..." /> {/* Unwanted mb-6 */}
</div>
Problems:
- Last item has unwanted bottom margin
- Can't change list spacing without modifying component
- Internal
mb-2hard to override
✅ After (Parent-Controlled)
function ListItem({ title, description }: Props) {
return (
<div className="p-4 border rounded">
<div className="space-y-2">
<h3 className="font-semibold">{title}</h3>
<p>{description}</p>
</div>
</div>
);
}
<div className="space-y-6">
<ListItem title="Item 1" description="..." />
<ListItem title="Item 2" description="..." />
<ListItem title="Item 3" description="..." />
</div>
// Different context: compact spacing
<div className="space-y-2">
<ListItem title="Item 1" description="..." />
<ListItem title="Item 2" description="..." />
</div>
Benefits:
- No unwanted margins
- Internal spacing controlled by
space-y-2 - Reusable with different spacings
Example 3: Form Fields
❌ Before (Mixed Strategy)
<form>
<div className="mb-4">
<Label htmlFor="name">Name</Label>
<Input id="name" className="mt-2" />
</div>
<div className="mb-4">
<Label htmlFor="email">Email</Label>
<Input id="email" className="mt-2" />
</div>
<Button type="submit" className="mt-6">Submit</Button>
</form>
Problems:
- Spacing scattered across children
- Hard to change consistently
- Have to remember
mt-6for button
✅ After (Parent-Controlled)
<form className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input id="name" />
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" />
</div>
<Button type="submit" className="mt-2">Submit</Button>
</form>
Benefits:
- Spacing controlled in 2 places: form (
space-y-4) and field groups (space-y-2) - Easy to change all field spacing at once
- Consistent and predictable
Anti-Patterns to Avoid
Anti-Pattern 1: Last Child Special Case
// ❌ WRONG
{items.map((item, index) => (
<Card key={item.id} className={index < items.length - 1 ? "mb-4" : ""}>
{item.name}
</Card>
))}
// ✅ CORRECT
<div className="space-y-4">
{items.map(item => (
<Card key={item.id}>{item.name}</Card>
))}
</div>
Anti-Pattern 2: Negative Margins to Fix Spacing
// ❌ WRONG - Using negative margin to fix unwanted spacing
<div className="-mt-4"> {/* Canceling out previous margin */}
<Card>Content</Card>
</div>
// ✅ CORRECT - Parent controls spacing
<div className="space-y-4">
<Card>Content</Card>
</div>
Why negative margins are bad:
- Indicates broken spacing strategy
- Hard to maintain
- Creates coupling between components
Anti-Pattern 3: Mixing Gap and Child Margins
// ❌ WRONG - Gap + child margins = unpredictable spacing
<div className="flex gap-4">
<Button className="mr-2">Cancel</Button> {/* gap + mr-2 = 24px */}
<Button>Save</Button>
</div>
// ✅ CORRECT - Only gap
<div className="flex gap-4">
<Button>Cancel</Button>
<Button>Save</Button>
</div>
// ✅ CORRECT - Exception case
<div className="flex gap-4">
<Button>Cancel</Button>
<Button className="mr-8">Save</Button> {/* Intentional extra space */}
<Button>Delete</Button>
</div>
Anti-Pattern 4: Using Margins for Layout
// ❌ WRONG - Using margins to create layout
<div>
<div className="ml-64"> {/* Pushing content for sidebar */}
Content
</div>
</div>
// ✅ CORRECT - Use proper layout (flex/grid)
<div className="flex gap-6">
<aside className="w-64">Sidebar</aside>
<main className="flex-1">Content</main>
</div>
Quick Reference
Spacing Method Cheat Sheet
| Use Case | Method | Example |
|---|---|---|
| Flex siblings | gap-* |
flex gap-4 |
| Grid siblings | gap-* |
grid gap-6 |
| Vertical stack | space-y-* |
space-y-4 |
| Horizontal stack | space-x-* |
space-x-2 |
| Inside component | p-* |
p-6 |
| One child exception | m-* |
mt-8 |
Common Spacing Values
| Class | Pixels | Usage |
|---|---|---|
gap-2 or space-y-2 |
8px | Tight (label + input) |
gap-4 or space-y-4 |
16px | Standard (form fields) |
gap-6 or space-y-6 |
24px | Sections (cards) |
gap-8 or space-y-8 |
32px | Large gaps |
p-4 |
16px | Standard padding |
p-6 |
24px | Card padding |
px-4 py-8 |
16px / 32px | Page padding |
Decision Flowchart (Simplified)
Need spacing?
│
├─ Between siblings?
│ ├─ Flex/Grid parent? → gap-*
│ └─ Regular parent? → space-y-* or space-x-*
│
├─ Inside component? → p-*
│
└─ Exception case? → m-* (sparingly)
Best Practices Summary
Do ✅
- Use parent-controlled spacing (
gap,space-y,space-x) - Use
gap-*for flex and grid layouts - Use
space-y-*for vertical stacks (forms, content) - Use
p-*for internal spacing (padding inside components) - Use margin only for exceptions (mt-8 to separate sections)
- Let components be context-agnostic (no built-in margins)
Don't ❌
- ❌ Add margins to reusable components
- ❌ Use last-child selectors or conditional margins
- ❌ Mix gap with child margins
- ❌ Use negative margins to fix spacing
- ❌ Use margins for layout (use flex/grid)
- ❌ Hard-code spacing in child components
Spacing Checklist
Before implementing spacing, verify:
- Parent controls children? Using gap or space-y/x?
- No child margins? Components don't have mb-* or mr-*?
- Consistent method? Not mixing gap + child margins?
- Reusable components? Work in different contexts?
- No edge cases? No last-child or first-child special handling?
- Semantic spacing? Using design system scale (4, 8, 12, 16...)?
Next Steps
- Practice: Refactor existing components to use parent-controlled spacing
- Explore: Interactive spacing examples
- Reference: Quick Reference Tables
- Layout Patterns: Layouts Guide
Related Documentation:
- Layouts - When to use Grid vs Flex
- Foundations - Spacing scale tokens
- Component Creation - Building reusable components
- Quick Start - Essential patterns
Last Updated: November 2, 2025