Clean Web Design System
This skill captures a professional, minimal design aesthetic for web front-ends. The style is characterized by restrained use of color, generous whitespace, crisp typography, and seamless light/dark mode transitions — the kind of design you'd see in a well-crafted SaaS dashboard or modern productivity tool.
The system is built on three pillars:
- •HSL CSS custom properties as a semantic color token layer
- •Tailwind CSS for utility-first styling
- •Component composition with small, reusable UI primitives (shadcn/ui style)
Read references/design-tokens.md for the complete color token system and CSS/Tailwind setup.
Read references/component-patterns.md for copy-pasteable component code.
Philosophy
Every pixel of border, shadow, and color should serve a purpose. The palette is intentionally narrow — mostly neutrals with a single primary accent — so the user's content takes center stage. Dark mode is a first-class citizen achieved by swapping CSS custom property values, not by overriding individual styles.
If something doesn't need to be colorful, it shouldn't be. Text is the primary communicator. Color is reserved for status indicators (green = good, red = bad, amber = caution) and interactive affordances (primary buttons, active nav items, focus rings).
Tech Stack
The design system is framework-flexible but optimized for:
- •React with TypeScript
- •Tailwind CSS with
darkMode: ['class'] - •Lucide React for icons (16×16 at
h-4 w-4default) - •clsx + tailwind-merge via a
cn()utility for conditional class merging - •Radix UI for accessible unstyled primitives (dialogs, dropdowns)
- •D3.js or Recharts for data visualization
For other frameworks (Vue, Svelte, plain HTML), adapt the patterns but keep the same visual language. The color tokens, spacing, and typography choices transfer directly.
Color System
Colors are HSL values (without the hsl() wrapper) in CSS custom properties on :root and .dark. This lets Tailwind apply opacity modifiers like bg-primary/10.
There's no "blue-500" or "gray-300" here. Every color has a semantic name describing its purpose. This makes dark mode trivial — swap the variable values and every component updates automatically.
Core Tokens
| Token | Purpose | Light | Dark |
|---|---|---|---|
--background | Page background | white | near-black navy |
--foreground | Primary text | near-black navy | near-white |
--card / --card-foreground | Card surfaces & text | white / dark | dark / light |
--primary / --primary-foreground | Primary actions, emphasis | dark navy / near-white | near-white / dark navy |
--secondary / --secondary-foreground | Secondary surfaces | pale blue-gray / dark | dark blue-gray / light |
--muted / --muted-foreground | Muted backgrounds, subdued text | pale / medium gray | dark / lighter gray |
--accent / --accent-foreground | Hover states, active nav | pale / dark | dark / light |
--destructive / --destructive-foreground | Error, danger | red / white | muted red / white |
--border | All borders | light gray | dark blue-gray |
--input | Input borders | light gray | dark blue-gray |
--ring | Focus rings | dark navy | light gray |
--radius | Border radius base | 0.5rem | 0.5rem |
See references/design-tokens.md for exact HSL values and the full CSS setup.
Status Colors
For semantic indicators outside the token system, use Tailwind colors with dark variants:
- •Success:
text-green-600 bg-green-100/dark:text-green-400 dark:bg-green-900 - •Error:
text-red-600 bg-red-100/dark:text-red-400 dark:bg-red-900 - •Warning:
text-amber-600 bg-amber-100/dark:text-amber-400 dark:bg-amber-900 - •Info:
text-blue-600 bg-blue-100/dark:text-blue-400 dark:bg-blue-900
Badge pattern: bg-{color}-100 text-{color}-800 dark:bg-{color}-900 dark:text-{color}-200
Typography
System font stack (no custom fonts — keeps things fast and native-feeling).
| Element | Classes | When to use |
|---|---|---|
| Page title | text-3xl font-bold tracking-tight | Top of each page |
| Page subtitle | text-muted-foreground | Below page title |
| Card/section title | text-2xl font-semibold leading-none tracking-tight | CardTitle component |
| Subsection header | text-lg font-semibold | Detail panel headers |
| Section label | text-base font-medium | Smaller headings |
| Body text | text-sm | Default content size |
| Small/metadata | text-xs text-muted-foreground | Labels, timestamps, captions |
| KPI / hero number | text-2xl font-bold | Statistics, large values |
Key rules: body text is always text-sm. Metadata and secondary info is always text-xs text-muted-foreground. Statistics are text-2xl font-bold. Small section labels within cards pair an icon with text-xs text-muted-foreground.
Spacing & Layout
Page Structure
Sidebar (fixed w-64, border-r, bg-card) | Main (pl-64, p-8 inner)
| └─ space-y-6 between sections
Grid Patterns
- •KPI cards:
grid gap-4 md:grid-cols-2 lg:grid-cols-4 - •Content grid:
grid gap-4 md:grid-cols-2 lg:grid-cols-3 - •Stat row in card:
grid grid-cols-3 gap-4 - •Card directory:
grid gap-4 md:grid-cols-2 lg:grid-cols-3
Spacing Scale
- •Card padding:
p-6(standard) /p-4(compact) - •Between sections:
space-y-6 - •Between items in list:
space-y-2orspace-y-3 - •Icon-to-label gap:
gap-1.5(tight) /gap-2(normal) /gap-3(spacious) - •Tag wrapping:
flex flex-wrap gap-1.5 - •Filter chips:
gap-3
Components
See references/component-patterns.md for full component code. Quick reference:
Cards
The foundational surface — everything lives in a card.
- •Base:
rounded-lg border bg-card text-card-foreground shadow-sm - •Interactive: add
hover:bg-muted/50 transition-colors cursor-pointer - •Structure:
Card > CardHeader > CardTitle + CardDescription > CardContent > CardFooter
Buttons
Six variants (default, destructive, outline, secondary, ghost, link) × four sizes (default, sm, lg, icon). Icons: h-4 w-4 mr-2 before text.
Inputs
h-10 rounded-md border border-input bg-background px-3 py-2 text-sm with focus ring and disabled states. Labels: block text-sm font-medium mb-2. Help text: text-xs text-muted-foreground mt-1.
Navigation
Sidebar items: flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors. Active: bg-accent text-accent-foreground. Inactive: text-muted-foreground hover:bg-accent hover:text-accent-foreground.
Badges & Pills
- •Status:
inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded - •Topic:
text-xs bg-secondary px-2 py-1 rounded-full - •Tag:
text-xs bg-primary/10 text-primary px-2 py-1 rounded-full
Avatars (Initials)
flex items-center justify-center shrink-0 rounded-full bg-primary/10 font-medium
Sizes: h-6 w-6 text-xs / h-8 w-8 text-sm / h-10 w-10 text-sm / h-14 w-14 text-lg
Loading States
- •Skeleton:
bg-muted animate-pulse rounded(sized to match content) - •Spinner:
animate-spin rounded-full h-8 w-8 border-b-2 border-primary - •Inline:
text-muted-foregroundwith "Loading..." text
Empty States
flex items-center justify-center h-32 text-muted-foreground with a simple message.
Error States
Container: rounded-lg border border-destructive/50 bg-destructive/10 p-4, text: text-sm text-destructive
Data Visualization
Charts use the CSS custom properties for automatic theme integration:
- •Line/area stroke:
hsl(var(--primary)) - •Area fill:
hsl(var(--primary) / 0.2) - •Bar fill:
hsl(var(--primary)), hover:hsl(var(--primary) / 0.8) - •Bar corners:
rx: 4 - •Axis text: CSS class
fill-muted-foreground text-xs - •Grid lines:
stroke: currentColor,stroke-opacity: 0.1 - •Tooltips:
background: hsl(var(--popover)),color: hsl(var(--popover-foreground)),border: 1px solid hsl(var(--border)),border-radius: 6px,padding: 8px 12px,font-size: 12px,box-shadow: 0 2px 8px rgba(0,0,0,0.1) - •Sparkline bars:
flex items-end gap-1 h-8, each barflex-1 bg-primary/20 rounded-t
Dark Mode Implementation
Toggle via .dark class on <html> using Tailwind's darkMode: ['class']. Store preference in localStorage. Toggle button in sidebar header using Moon/Sun lucide icons.
Use the dark: prefix only for colors outside the token system (status colors). For everything else — backgrounds, text, borders, cards, inputs — semantic token classes handle it automatically.
Accessibility
- •Focus rings:
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 - •Semantic HTML:
<nav>,<main>,<aside>,<button>, proper heading hierarchy - •Screen reader text:
<span className="sr-only">for icon-only buttons - •Aria labels on icon buttons
- •Disabled:
disabled:pointer-events-none disabled:opacity-50
Transitions
Keep animations subtle: transition-colors on hover states, animate-pulse for skeletons, animate-spin for spinners, duration-200 for modals. No flashy animations — the interface should feel responsive, not performative.