
The cleaner your UI looks, the more complex your theming system needs to be. Linear-style interfaces—those gradient-free, minimalist designs popularized by tools like Linear.app—seem deceptively simple. But beneath that clean aesthetic lies a sophisticated color token system that most developers get wrong.
The problem isn't just aesthetic. Poor theming creates genuine accessibility barriers: dark modes that fail contrast requirements, interactive elements that disappear for keyboard users, and color combinations that become unreadable under real-world conditions like screen glare or low vision.
The Hidden Complexity of "Simple" Design
Linear design systems rely heavily on subtle color relationships and precise contrast ratios. When you strip away gradients, shadows, and visual flourishes, every remaining element must pull its weight functionally. A button that depends on a slight color shift for its hover state might work fine in light mode but become invisible in dark mode.
<> "Linear designs amplify risks due to minimalism—subtle colors that work in controlled design environments often blend together in real-world usage scenarios."/>
The WCAG 2.2 requirements are non-negotiable: 4.5:1 contrast ratio for normal text, 3:1 for large text, and even higher standards for interactive elements. But achieving these ratios consistently across light, dark, and high-contrast modes requires a systematic approach to color token generation.
Building Bulletproof Color Scales
The key insight is treating color modes as 1:1 mappings, not separate design systems. Instead of designing light mode and then "adapting" it to dark, you need to build paired color scales from the ground up.
Start with your brand colors and extend them using LCH color space for perceptual uniformity. Generate 40-48 tokens covering:
- Primary/secondary/tertiary brand colors
- Semantic colors (success, error, warning)
- Neutral scales for text and backgrounds
Here's how the token structure should work:
1:root {
2 /* Light mode tokens */
3 --primary-400: #2563eb;
4 --primary-600: #1d4ed8;
5 --neutral-50: #f9fafb;
6 --neutral-900: #111827;
7
8 /* Semantic mappings */The magic happens in that semantic layer. Your components reference --button-bg, never --primary-400 directly. This abstraction lets you maintain consistent relationships across modes while adapting the underlying values.
Dark Mode Isn't Just Inverted Colors
Dark mode requires rethinking visual hierarchy entirely. In light mode, you can rely on shadows and layering. In dark mode, elevation works through lighter surfaces over darker backgrounds—no muddy shadows that reduce contrast.
Interactive states become critical. That subtle hover effect that works in light mode? In dark mode, you need visible borders, outline rings, or more pronounced color shifts:
1.button {
2 background: var(--button-bg);
3 color: var(--button-text);
4 border: 1px solid transparent;
5 transition: all 0.2s ease;
6}
7
8.button:hover {Implementation Strategy
For mode switching, you have two main approaches:
Class-based switching gives you more control:
1const toggleTheme = () => {
2 document.body.classList.toggle('dark');
3 localStorage.setItem('theme',
4 document.body.classList.contains('dark') ? 'dark' : 'light'
5 );
6};Media query approach respects system preferences:
1@media (prefers-color-scheme: dark) {
2 :root {
3 /* Apply dark tokens */
4 }
5}Many systems combine both, defaulting to system preference but allowing user override.
Testing in the Real World
The biggest mistake is testing only in ideal conditions. Your carefully crafted contrast ratios might work perfectly on a calibrated monitor in a dark room but fail completely on a phone screen in bright sunlight.
Test scenarios should include:
- Various screen brightness levels
- Different device types and screen qualities
- Users with low vision or color vision differences
- Keyboard-only navigation
- Screen readers and other assistive technologies
<> "Poor theming leads to inaccessible UIs: dark modes often fail contrast requirements, causing eye strain for users with photophobia, reduced readability for low vision, or invisible navigation for keyboard users."/>
Working with UI Libraries
If you're using libraries like shadcn/ui or Radix, you get pre-audited tokens as a starting point. But customization still requires understanding these principles. Map your brand colors to the library's semantic tokens rather than overriding individual components.
For high-contrast mode, you'll need a third token set that works with forced-colors media queries—essentially designing for Windows High Contrast mode and similar accessibility features.
Why This Matters
Accessible design isn't just compliance theater. It's about building products that work for everyone, in every condition. Linear design's minimalist aesthetic is powerful precisely because it removes visual noise—but that means every remaining element must be functionally bulletproof.
The upfront investment in a proper token system pays dividends in maintainability. Instead of debugging individual component contrast issues across modes, you fix problems at the token level and everything updates automatically.
Start with your current design system and audit it across modes. Are your interactive states visible? Do text contrast ratios hold up? Can users navigate by keyboard in both light and dark themes? The answers will guide your token restructuring priorities.
