Color Science for Interface Designers: What I Learned Building a Zero-Color Site
The W3C Web Content Accessibility Guidelines require a minimum contrast ratio of 4.5:1 for normal text, yet a 2023 WebAIM survey found that 83.6% of the top one million websites had detectable WCAG 2 contrast failures on their home pages. I built blakecrosley.com with the opposite problem: maximum contrast (21:1) everywhere, then dialing it back selectively.1
TL;DR
I designed my personal site with zero brand colors. The entire visual hierarchy operates through white text on absolute black (#000000) at four opacity tiers: 100%, 65%, 40%, and 10%. The decision forced me to learn perceptual color science — why sRGB lies about uniform spacing, how OKLCH solves it, and why dark mode requires different contrast relationships than light mode. The interactive tools below let you explore contrast ratios and color space differences. The takeaway: understanding the science behind color perception produces better design decisions than aesthetic intuition.
My Color Non-Palette
Most design systems start with a color palette. Mine starts with the absence of one:
:root {
--color-bg-dark: #000000;
--color-bg-elevated: #111111;
--color-bg-surface: #1a1a1a;
--color-text-primary: #ffffff;
--color-text-secondary: rgba(255,255,255,0.65);
--color-text-tertiary: rgba(255,255,255,0.4);
--color-border: rgba(255,255,255,0.1);
--color-border-subtle: rgba(255,255,255,0.05);
--color-accent: #ffffff;
}
Ten tokens. No brand colors. No semantic error/success/warning palette. The entire visual hierarchy operates through four transparency tiers.2
Why Four Tiers Work
Each tier serves a specific communication function:
| Tier | Opacity | CSS Token | Function | WCAG Ratio (on #000) |
|---|---|---|---|---|
| Primary | 100% | --color-text-primary |
Headlines, body text, critical content | 21:1 (AAA) |
| Secondary | 65% | --color-text-secondary |
Subtitles, navigation, metadata | 13.7:1 (AAA) |
| Tertiary | 40% | --color-text-tertiary |
Timestamps, helper text, disabled states | 8.4:1 (AAA) |
| Structural | 10% | --color-border |
Borders, dividers, background separation | N/A (non-text) |
Every tier passes WCAG AAA (7:1) for text contrast. The tertiary tier at 40% opacity produces an 8.4:1 ratio — nearly double the AA minimum of 4.5:1. The brutalist choice of absolute black (#000000 rather than #0a0a0a or #1a1a1a) gives every text tier maximum headroom.3
The --spacing-2xs Incident
I discovered the value of strict design tokens when I used --spacing-2xs for a subtitle margin. The token did not exist in my :root definition. The CSS silently failed, the layout broke, and I spent 20 minutes debugging a spacing issue that should have been a compile-time error. The fix: change to --spacing-xs (the smallest token I defined). The lesson: if a value does not exist in the system, the design is wrong, not the system.4
Why sRGB Lies to Designers
The Perceptual Non-Uniformity Problem
sRGB (the standard color space for web) maps colors to a cube where each axis (red, green, blue) ranges from 0-255. Moving 50 units in the red channel does not produce the same perceived change as moving 50 units in the green channel. Human eyes contain more green-sensitive cones than red or blue, making green shifts more perceptible than equivalent red shifts.5
The practical consequence: a designer who creates a color palette by evenly spacing hex values produces colors that look unevenly spaced. The “medium” gray between #000000 and #FFFFFF is not #808080 (mathematical midpoint) but approximately #777777 (perceptual midpoint), because human brightness perception follows a power law rather than a linear function.6
My site sidesteps the problem entirely. By using only white at varying opacities, I avoid the sRGB uniformity trap. Opacity scales linearly with perceived transparency against a black background — a property that sRGB color mixing does not share.
The OKLCH Solution
OKLCH (Oklab Lightness, Chroma, Hue) is a perceptually uniform color space where equal mathematical distances correspond to equal perceived differences. A 10-unit lightness step always looks like the same amount of change regardless of the starting color.7
/* sRGB: mathematically even, perceptually uneven */
--gray-100: #f5f5f5;
--gray-200: #e5e5e5;
--gray-300: #d4d4d4;
/* OKLCH: perceptually even steps */
--gray-100: oklch(96% 0 0);
--gray-200: oklch(88% 0 0);
--gray-300: oklch(80% 0 0);
Modern CSS supports oklch() natively. For my next project that requires a color palette, I will define the palette in OKLCH. For my current site, the opacity-based system achieves the same perceptual uniformity by different means.
My Dark Mode Decision: No Light Mode
My site has no prefers-color-scheme media query. It operates exclusively in dark mode. The decision was deliberate.8
The argument for dual modes: Users expect a light mode option. System preference integration respects user choice.
The argument against (which I chose): Maintaining two visual systems inevitably compromises both. A design that works at 65% opacity on black requires a different opacity on white (closer to 45%) to achieve the same perceptual weight. Every interaction state, every border, every shadow needs recalibration. I chose to design one system well rather than two systems adequately.
The absolute black background (#000000) maximizes the contrast available to every text tier:
/* My actual typography contrast hierarchy */
.hero__title { color: var(--color-text-primary); } /* 21:1 */
.hero__subtitle { color: var(--color-text-secondary); } /* 13.7:1 */
.nav a { color: var(--color-text-secondary); } /* 13.7:1 */
.nav a:hover { color: var(--color-text-primary); } /* 21:1 */
The hover state transition (secondary → primary) provides functional feedback entirely through contrast change — no color shift needed.
Contrast and Readability
WCAG Contrast Requirements
| Level | Normal Text (< 18pt) | Large Text (≥ 18pt or 14pt bold) |
|---|---|---|
| AA | 4.5:1 | 3:1 |
| AAA | 7:1 | 4.5:1 |
The contrast ratio measures the relative luminance difference between foreground and background colors. A ratio of 1:1 means no contrast (identical colors). A ratio of 21:1 means maximum contrast (black on white or white on black).9
Beyond WCAG: APCA
The WCAG 2 contrast algorithm has known limitations. The algorithm treats all colors equally regardless of polarity (dark-on-light vs. light-on-dark), despite research showing that human perception of contrast differs significantly between the two modes.10
APCA (Accessible Perceptual Contrast Algorithm) addresses these limitations by accounting for: - Polarity sensitivity: Dark text on light backgrounds is more readable than light text on dark backgrounds at the same mathematical contrast ratio - Spatial frequency: Thin fonts require more contrast than bold fonts at the same size - Adaptation: The eye adapts to the surrounding page luminance, affecting perceived contrast
APCA is expected to form the basis for WCAG 3.0 contrast requirements. My site benefits from the polarity insight: since I use light-on-dark exclusively, I need higher contrast ratios than a light-mode site. My lowest text tier (40% opacity, 8.4:1 ratio) exceeds even APCA’s recommended minimum for body text on dark backgrounds.
Semantic Color Without Color
Production color systems typically assign colors to functions (green for success, red for error). My site avoids functional color entirely because it has no transactional UI — no forms, no status messages, no success/error states. The content is static.
If I needed semantic color, I would add it surgically:
| Function | Typical Approach | My Hypothetical Approach |
|---|---|---|
| Success | Green | White text + checkmark icon |
| Error | Red | White text + X icon + border emphasis |
| Warning | Amber | White text + exclamation icon |
Pairing icons with text eliminates the color-only communication that fails for the approximately 8% of males with color vision deficiency. The approach also preserves my monochromatic system. Color would serve as an accent, not a structural element.11
The Typography-First Hierarchy
With no color carrying hierarchy, typography carries everything on my site:
:root {
--font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text",
"SF Pro Display", "Helvetica Neue", Arial, sans-serif;
--font-size-display: 5rem; /* 80px — hero headlines */
--font-size-7xl: 3.875rem; /* 62px */
--font-size-base: 1rem; /* 16px — body text */
--font-size-xs: 0.75rem; /* 12px — metadata */
}
System fonts, not custom web fonts. The choice is both a brutalist decision (use the platform’s native material) and a performance decision (zero font-loading latency, contributing to 100/100 Lighthouse scores). The display size (80px) with tight letter-spacing (-0.03em) gives headlines gravitas without decoration. Body text at 16px with generous line-height (1.7) prioritizes readability over density.12
The 13-step type scale from 0.75rem to 5rem provides enough granularity to express hierarchy through size alone. Combined with the four opacity tiers, I have 52 potential combinations (13 sizes × 4 opacities) — more than enough to express any content hierarchy without reaching for color.
Key Takeaways
For designers: - Define color palettes in OKLCH rather than sRGB; perceptually uniform color spaces produce predictable hierarchy and consistent contrast ratios across the palette - Test your tertiary text tier against WCAG AAA (7:1), not just AA (4.5:1); the AAA threshold provides enough headroom for real-world screen conditions (low brightness, glare, aging displays) - Consider whether your project actually needs color; my site proves that typography and opacity alone can carry a complete visual hierarchy
For developers:
- Use CSS oklch() for color definitions and test contrast ratios with both WCAG 2 (current standard) and APCA (upcoming standard); browser support for oklch() is available in all modern browsers
- Implement dark mode by adjusting lightness and saturation in OKLCH space rather than inverting hex values; perceptual adjustment produces better results than mathematical inversion
- Strict design token enforcement prevents silent CSS failures; if a token does not exist, the design should change, not the token system
References
-
WebAIM, “The WebAIM Million: 2023 Accessibility Analysis,” 2023. ↩
-
Author’s CSS custom properties from
critical.css. 10 color tokens, all derived from white-on-black opacity relationships. ↩ -
Author’s WCAG contrast calculations. Primary (21:1), secondary (13.7:1), tertiary (8.4:1), all exceeding AAA 7:1 minimum. ↩
-
Author’s debugging experience.
--spacing-2xswas used but never defined in:root. Documented in MEMORY.md error entries. ↩ -
Hunt, R.W.G., The Reproduction of Colour, Wiley, 2004. ↩
-
Poynton, Charles, Digital Video and HD, Morgan Kaufmann, 2012. Gamma correction and perceptual linearity. ↩
-
Ottosson, Bjorn, “A perceptual color space for image processing,” 2020. OKLCH specification. ↩
-
Author’s design decision. Single dark mode avoids visual compromise inherent in maintaining parallel light/dark systems. ↩
-
W3C, “Web Content Accessibility Guidelines (WCAG) 2.1,” 2018. ↩
-
Somers, Andrew, “APCA Contrast Calculator,” 2023. ↩
-
W3C, “WCAG 2.1 Success Criterion 1.4.1: Use of Color,” 2018. ↩
-
Author’s typography system. 13-step font scale from 0.75rem (12px) to 5rem (80px). System font stack eliminates FOIT/FOUT. ↩