color-and-contrast.md

  1# Color & Contrast
  2
  3## Color Spaces: Use OKLCH
  4
  5**Stop using HSL.** Use OKLCH (or LCH) instead. It's perceptually uniform, meaning equal steps in lightness *look* equal, unlike HSL where 50% lightness in yellow looks bright while 50% in blue looks dark.
  6
  7The OKLCH function takes three components: `oklch(lightness chroma hue)` where lightness is 0-100%, chroma is roughly 0-0.4, and hue is 0-360. To build a primary color and its lighter / darker variants, hold the chroma+hue roughly constant and vary the lightness, but **reduce chroma as you approach white or black**, because high chroma at extreme lightness looks garish.
  8
  9The hue you pick is a brand decision and should not come from a default. Do not reach for blue (hue 250) or warm orange (hue 60) by reflex; those are the dominant AI-design defaults, not the right answer for any specific brand.
 10
 11## Building Functional Palettes
 12
 13### Tinted Neutrals
 14
 15**Pure gray is dead.** A neutral with zero chroma feels lifeless next to a colored brand. Add a tiny chroma value (0.005-0.015) to all your neutrals, hued toward whatever your brand color is. The chroma is small enough not to read as "tinted" consciously, but it creates subconscious cohesion between brand color and UI surfaces.
 16
 17The hue you tint toward should come from THIS project's brand, not from a "warm = friendly, cool = tech" formula. If your brand color is teal, your neutrals lean toward teal. If your brand color is amber, they lean toward amber. The point is cohesion with the SPECIFIC brand, not a stock palette.
 18
 19**Avoid** the trap of always tinting toward warm orange or always tinting toward cool blue. Those are the two laziest defaults and they create their own monoculture across projects.
 20
 21### Palette Structure
 22
 23A complete system needs:
 24
 25| Role | Purpose | Example |
 26|------|---------|---------|
 27| **Primary** | Brand, CTAs, key actions | 1 color, 3-5 shades |
 28| **Neutral** | Text, backgrounds, borders | 9-11 shade scale |
 29| **Semantic** | Success, error, warning, info | 4 colors, 2-3 shades each |
 30| **Surface** | Cards, modals, overlays | 2-3 elevation levels |
 31
 32**Skip secondary/tertiary unless you need them.** Most apps work fine with one accent color. Adding more creates decision fatigue and visual noise.
 33
 34### The 60-30-10 Rule (Applied Correctly)
 35
 36This rule is about **visual weight**, not pixel count:
 37
 38- **60%**: Neutral backgrounds, white space, base surfaces
 39- **30%**: Secondary colors: text, borders, inactive states
 40- **10%**: Accent: CTAs, highlights, focus states
 41
 42The common mistake: using the accent color everywhere because it's "the brand color." Accent colors work *because* they're rare. Overuse kills their power.
 43
 44## Contrast & Accessibility
 45
 46### WCAG Requirements
 47
 48| Content Type | AA Minimum | AAA Target |
 49|--------------|------------|------------|
 50| Body text | 4.5:1 | 7:1 |
 51| Large text (18px+ or 14px bold) | 3:1 | 4.5:1 |
 52| UI components, icons | 3:1 | 4.5:1 |
 53| Non-essential decorations | None | None |
 54
 55**The gotcha**: Placeholder text still needs 4.5:1. That light gray placeholder you see everywhere? Usually fails WCAG.
 56
 57### Dangerous Color Combinations
 58
 59These commonly fail contrast or cause readability issues:
 60
 61- Light gray text on white (the #1 accessibility fail)
 62- **Gray text on any colored background**: gray looks washed out and dead on color. Use a darker shade of the background color, or transparency
 63- Red text on green background (or vice versa): 8% of men can't distinguish these
 64- Blue text on red background (vibrates visually)
 65- Yellow text on white (almost always fails)
 66- Thin light text on images (unpredictable contrast)
 67
 68### Never Use Pure Gray or Pure Black
 69
 70Pure gray (`oklch(50% 0 0)`) and pure black (`#000`) don't exist in nature; real shadows and surfaces always have a color cast. Even a chroma of 0.005-0.01 is enough to feel natural without being obviously tinted. (See tinted neutrals example above.)
 71
 72### Testing
 73
 74Don't trust your eyes. Use tools:
 75
 76- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
 77- Browser DevTools → Rendering → Emulate vision deficiencies
 78- [Polypane](https://polypane.app/) for real-time testing
 79
 80## Theming: Light & Dark Mode
 81
 82### Dark Mode Is Not Inverted Light Mode
 83
 84You can't just swap colors. Dark mode requires different design decisions:
 85
 86| Light Mode | Dark Mode |
 87|------------|-----------|
 88| Shadows for depth | Lighter surfaces for depth (no shadows) |
 89| Dark text on light | Light text on dark (reduce font weight) |
 90| Vibrant accents | Desaturate accents slightly |
 91| White backgrounds | Never pure black; use dark gray (oklch 12-18%) |
 92
 93In dark mode, depth comes from surface lightness, not shadow. Build a 3-step surface scale where higher elevations are lighter (e.g. 15% / 20% / 25% lightness). Use the SAME hue and chroma as your brand color (whatever it is for THIS project; do not reach for blue) and only vary the lightness. Reduce body text weight slightly (e.g. 350 instead of 400) because light text on dark reads as heavier than dark text on light.
 94
 95### Token Hierarchy
 96
 97Use two layers: primitive tokens (`--blue-500`) and semantic tokens (`--color-primary: var(--blue-500)`). For dark mode, only redefine the semantic layer; primitives stay the same.
 98
 99## Alpha Is A Design Smell
100
101Heavy use of transparency (rgba, hsla) usually means an incomplete palette. Alpha creates unpredictable contrast, performance overhead, and inconsistency. Define explicit overlay colors for each context instead. Exception: focus rings and interactive states where see-through is needed.
102
103---
104
105**Avoid**: Relying on color alone to convey information. Creating palettes without clear roles for each color. Using pure black (#000) for large areas. Skipping color blindness testing (8% of men affected).