8.3 KiB
8.3 KiB
Color — Tokens, Palettes, Restraint
Color is punctuation, not wallpaper. One accent, many neutrals, used surgically.
The Token System
Every project defines these tokens. No raw hex in components.
:root {
/* Surface (background) */
--surface: ...; /* primary background */
--surface-elevated: ...; /* cards, modals — slightly different */
--surface-sunken: ...; /* inputs, code blocks — slightly darker/lighter */
/* Ink (text) */
--ink: ...; /* primary text */
--ink-muted: ...; /* secondary text */
--ink-subtle: ...; /* tertiary, placeholders */
/* Lines */
--hairline: ...; /* borders, dividers, rules */
--hairline-strong: ...; /* emphasized borders */
/* Accent */
--accent: ...; /* the brand color */
--accent-ink: ...; /* text on accent surfaces */
--accent-soft: ...; /* tinted backgrounds for accent states */
/* State */
--success: ...;
--warning: ...;
--error: ...;
--info: ...;
}
Neutral Palette Library
Pick ONE neutral system. Then add an accent.
Bright / Paper (Refined Minimal, Editorial, Soft)
--surface: #FFFFFF /* or #FAFAFA */
--surface-elevated: #FFFFFF
--surface-sunken: #F7F7F5
--ink: #0A0A0A
--ink-muted: #6B6B6B
--ink-subtle: #A3A3A3
--hairline: #EAEAEA
--hairline-strong:#D4D4D4
Warm / Cream (Editorial, Soft)
--surface: #FAF6F0
--surface-elevated: #FFFFFF
--surface-sunken: #F0EBE3
--ink: #1A1714
--ink-muted: #6B5E51
--ink-subtle: #9C8E7E
--hairline: #E5DDD0
--hairline-strong:#D4C9B6
Deep / Ink (Technical, Brutalist, Editorial)
--surface: #0E0E0E
--surface-elevated: #161616
--surface-sunken: #050505
--ink: #F5F5F5
--ink-muted: #A3A3A3
--ink-subtle: #6B6B6B
--hairline: #262626
--hairline-strong:#3D3D3D
Cold / Stone (Swiss, Technical)
--surface: #F4F4F2
--surface-elevated: #FFFFFF
--surface-sunken: #ECECEA
--ink: #1A1A1A
--ink-muted: #595959
--ink-subtle: #8C8C8C
--hairline: #DCDCD8
--hairline-strong:#C2C2BD
True Black (Brutalist, Manifestos)
--surface: #000000
--surface-elevated: #0A0A0A
--surface-sunken: #000000
--ink: #FFFFFF
--ink-muted: #B3B3B3
--ink-subtle: #808080
--hairline: #1F1F1F
--hairline-strong:#404040
Accent Library
Pick ONE. Use it on 5–10% of pixels max. If you find yourself using it everywhere, it's not an accent — it's a brand color that needs a different neutral system.
Refined Minimal accents
- Linear-style purple:
#5E6AD2(with#0A0A0Aink) - Stripe indigo:
#635BFF - Mercury green:
#1B4332 - Cron red-orange:
#E0533D - Vercel on white: no accent — pure black ink IS the accent
Editorial accents
- Editorial red:
#C8281Cor#A91D1D - Newspaper yellow:
#E6B800(used as mark, not fill) - Ink blue:
#1B3A5C
Swiss accents
- Müller-Brockmann red:
#E63946or#D62828 - Electric blue:
#0066FF - Often no accent. Pure monochrome.
Brutalist accents
- Hot pink:
#FF3EA5 - Hazard yellow:
#FFE600 - Toxic green:
#39FF14 - Often used in block shapes, not fine details
Soft / Warm accents
- Terracotta:
#C65D3A - Sage:
#7A8471 - Dusty blue:
#5C7A8A - Mustard:
#C99632 - Plum:
#6B3D5C
Technical accents
- Terminal green:
#00FF66or#00CC66(softer) - Amber:
#FFB000 - Cyan:
#00C2FF - Hot pink (Vercel-style):
#FF0080
Playful accents
- Multi-hue palette — pick 3–4 working together:
- Coral
#FF6B6B+ Mustard#FFC857+ Teal#3DCCC7+ Plum#5B5F97 - Or simpler 2-color: Lime
#C5E063+ Deep Navy#1A1A40
- Coral
How to Use the Accent
The 5–10% rule
If the accent fills more than 10% of the page, it's no longer an accent. It's a brand background. Pick a different neutral system or reduce accent usage.
Where accents go
- ✅ Primary CTA button (one per page)
- ✅ Active nav item, current page marker
- ✅ Links (or use ink color with underline)
- ✅ Focus rings
- ✅ Key data point in a statistic block
- ✅ A small mark (a dot, a bar, a single character)
- ✅ Selected state in a list
- ✅ Logo
Where accents DON'T go
- ❌ Hero background
- ❌ Section backgrounds (full-bleed tints)
- ❌ Every card border
- ❌ Every icon
- ❌ Multiple CTA buttons on the same page (pick the one that matters)
- ❌ Body text (links are the exception)
- ❌ Drop shadows (use ink, not accent)
- ❌ Every heading
Contrast (WCAG)
| Use | Min ratio | Aim for |
|---|---|---|
| Body text | 4.5:1 (AA) | 7:1 (AAA) |
| Large text (18px+ or 14px bold+) | 3:1 (AA) | 4.5:1+ |
| UI components, icons | 3:1 | 4.5:1+ |
| Non-essential decorative | none | — |
| Focus rings | 3:1 vs adjacent | visible |
Tools: Stark (Figma plugin), WebAIM Contrast Checker, Polypane.
Rule of thumb:
- Pure black
#000on pure white#FFF= 21:1 #0A0A0Aon#FFFFFF= 19.4:1#6B6B6Bon#FFFFFF= 5.7:1 (acceptable for secondary text)#A3A3A3on#FFFFFF= 2.8:1 (only for placeholders, never for real text)#5E6AD2on#FFFFFF= 5.1:1 (acceptable as text or UI)
Dark Mode
Dark mode is not "invert the colors." Build it intentionally.
Principles
- Don't use pure black
#000for surfaces. It creates harsh contrast against text. Use#0E0E0Eor#121212— there's a reason Material Design picked these. - Don't use pure white
#FFFfor text. Soften to#F5F5F5or#EDEDED. - Reduce contrast slightly — text doesn't need to be 21:1 on dark. Aim for 12:1+ (more comfortable).
- Accents usually brighten in dark mode. A
#5E6AD2purple becomes#7B85E6or#8B95FF. - Shadows become subtle borders or glows. Dark UIs rarely use shadows; they use hairlines and elevation via lighter surfaces.
Token approach
:root {
/* Light */
--surface: #FFFFFF;
--ink: #0A0A0A;
/* ... */
}
[data-theme="dark"] {
--surface: #0E0E0E;
--ink: #F5F5F5;
/* Don't redefine everything — only invert what needs inverting */
}
Dark mode anti-patterns
- ❌ Pure black
#000background (harsh, increases eye strain) - ❌ Pure white
#FFFtext (vibrates against dark backgrounds) - ❌ Same accent as light mode (often too dark to read)
- ❌ Drop shadows that were already wrong in light mode (now invisible)
- ❌ Inverting images with CSS
filter: invert()(breaks photos)
Gradients
Default: don't use them.
When gradients ARE appropriate
- Hero text on dark backgrounds (subtle, low-contrast, mostly for atmosphere)
- Loading states / skeleton screens
- Data visualization (color scales)
- Photo overlays (dark gradient over image for legibility)
When gradients are NOT appropriate
- ❌ Hero backgrounds (the #1 AI slop signal)
- ❌ CTA buttons
- ❌ Section dividers
- ❌ "Mesh gradient" backgrounds
- ❌ Animated gradient backgrounds
- ❌ Purple → pink → orange "sunset" effects
- ❌ Multi-stop gradients on text
If you must use one
/* Subtle, dark, for atmosphere only */
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.4) 100%
);
/* Image overlay */
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0.2) 0%,
rgba(0, 0, 0, 0.8) 100%
);
Avoid: linear-gradient(135deg, #667eea 0%, #764ba2 100%) and all its cousins.
Color Anti-Patterns
| ❌ Don't | ✅ Do |
|---|---|
#667eea → #764ba2 purple gradient hero |
White background, ink-black headline |
| Multiple accent colors competing | One accent, used 5–10% |
#999 gray for body text |
Use a tested muted ink (#6B6B6B+) |
Random hex everywhere (#3B82F6 next to #1D4ED8) |
Token system, semantic names |
| Color-coded everything (red/yellow/green for non-state things) | Restraint. State colors for state only. |
| Hard-coded brand colors in components | Use --accent token |
| Inverting colors for dark mode | Re-tune the palette, don't invert |
| Tint backgrounds behind every paragraph | White space, not tinted space |
| Box-shadows in accent color | Ink-colored shadows, or no shadows |
| Stock-photo color overlays | Let photos speak, use overlays only for legibility |
| 4 brand colors in the logo, used equally | One brand color + a system of neutrals |