One density per page
Pick comfortable or compact per page, and one quiet icon size.
From shadcn (Vercel plugin): one density system, quiet icons, Geist Mono.
One dashboard, two spacings
Show guides, reshuffle left, switch density right.
Arbitrary per cardBad
One densityGood
const DENSITY = {
comfortable: { gap: "gap-6", pad: "p-6", text: "text-sm" },
compact: { gap: "gap-4", pad: "p-4", text: "text-sm" },
} as const;
const d = DENSITY.comfortable;
<main className={cn("grid", d.gap, d.pad, d.text)}>
<section className={cn("grid grid-cols-2", d.gap)}>
<Card className={d.pad}>…</Card>
<Card className={d.pad}>…</Card>
</section>
</main>
Icon sizes in a toolbar
Reshuffle the left; pick 16 or 20px on the right.
Whatever looked rightBad
One sizeGood
import { Search, Filter, Bell, Download } from "lucide-react";
<Button variant="ghost" size="icon" aria-label="Search">
<Search className="h-4 w-4" />
</Button>
Mono for metrics and IDs
Type an amount; it lands in both tables.
UI font everywhereBad
| Invoice | Time | Amount |
|---|
font-sansMono for figuresGood
| Invoice | Time | Amount |
|---|
font-mono tabular-nums text-right<TableCell className="font-mono">{invoice.number}</TableCell>
<TableCell className="font-mono text-muted-foreground">{time}</TableCell>
<TableCell className="text-right font-mono tabular-nums">
{formatCurrency(invoice.amount)}
</TableCell>
Why it matters, pros and cons
Why it matters
One spacing value lines up edges; almost-aligned reads as a mistake.
Pros
- Two numbers instead of one per component.
- Comfortable to compact is one token change.
- One icon size keeps stroke weight constant.
Cons
- Inner gaps (label to value) use a smaller scale.
- Dense tables may need rows tighter than
p-4. - Marketing pages want a looser rhythm than product UI.