Light Theme
The other half of the product. Every surface is white, so lightness cannot express depth — shadows and hairlines do all of the work, and the brand colour has to get darker.
Everything below is the real component. Change the controls, tab through it, and turn on Inspector Mode to read any value off the screen.
Invoice 2026-0431
Due 26 July · $1,428.00
Automatic payment is enabled
Depth without lightness
Four elevation levels on a single white surface. Remove the shadows and the hierarchy disappears completely — the opposite of dark mode.
Colour has to darken
Top row is the dark-theme value on white. Bottom row is the light-theme value. Same role, different ramp step, and only one of them passes contrast.
The inset trap
A well in dark mode is darker than its container. Copying that logic into light mode produces a muddy grey box — the border should carry the boundary instead.
Interaction states
Every state a user can put this component into, rendered side by side. If a state is missing here, it is missing in production too.
Every part, every measurement, and the reason it is that number.
Card on a sunken page
The page is grey; the card is white. That is the inverse of dark mode.
In light mode the page is often the darkest surface and the card is the lightest. Dark mode does the reverse — same tokens, opposite relationship.
- Sunken page#F4F6F8
A very slightly grey page makes a white card read as elevated with almost no shadow. It is the single cheapest depth trick in light UI.
- White card#FFFFFF + e2
Pure white is correct here — it is the top of the stack, not a text background competing with a light foreground.
- Hairlineink at 7% alpha
Does far more work than in dark mode. A shadow alone leaves a soft, undefined edge on white; the hairline is what makes it crisp.
- Inset#F7F8FA
Barely off-white. Going darker to mimic the dark theme produces a grey box that reads as disabled.
- ShadowSofter, wider, lower alpha
Light shadows spread further and sit at 6–16% rather than 32–66%. A dark-mode shadow on white looks like a hard, dirty smudge.
Values are read live from the running stylesheet, so this table can never drift from the code. Click any value to copy it.
Color
| Token | Value | Used for |
|---|---|---|
| --ds-canvas | Page background | |
| --ds-sunken | Page behind cards — makes white read as elevated | |
| --ds-surface | Cards, panels, dialogs — all the same white | |
| --ds-surface-inset | Inputs, code blocks, table headers | |
| --ds-fg | Primary text — 15.9:1 | |
| --ds-fg-secondary | Body text — 7.4:1 | |
| --ds-fg-muted | Captions — 4.9:1 | |
| --ds-border-subtle | Hairlines — the primary edge definition | |
| --ds-accent | Brand — one step darker than in dark mode | |
| --ds-danger-text | Error text on a pale tint — the 700 step | |
| --ds-layer-scrim | Behind modals — lighter than the dark-mode scrim |
Shadow
| Token | Value | Used for |
|---|---|---|
| --shadow-e1 … e5 | — | Softer and wider than their dark counterparts |
Inverted from dark — every surface is now mid-grey.
Pure black body text over several paragraphs is harsher than it looks in a single line.
Not a checklist to run at the end. These are the requirements the component was built from.
Contrast
- Light themes fail on the same pairs that dark themes pass, and vice versa. Every pair must be verified independently in each theme.
- The usual light-mode failure is a mid-tone brand colour with white text on the primary button. Check that one first.
- Hairlines at 7% alpha do not meet 3:1 and do not need to — they are decorative. Any border that is the only boundary of a control must be --ds-border or stronger.
Keyboard
| ⌘K → theme | The theme switch is in the command palette, reachable without a pointer. |
Screen readers
- Theme is invisible to assistive technology. It must never be a carrier of meaning.
Focus & touch
- The light focus ring is #6A55F2 — the same value as the accent, because on white it already clears 3:1 against both the button and the page.
- Light interfaces are typically used in bright environments where glare reduces effective contrast. Do not design to the AA minimum here — aim higher on anything read outdoors.
| Attribute | Applied to | Notes |
|---|---|---|
| color-scheme: light | [data-theme="light"] | Keeps native controls, scrollbars and autofill consistent when a light island sits inside a dark app. |
| prefers-color-scheme | Media query | Sets the initial value only. An explicit user choice must win and must persist. |
| forced-colors | Media query | High Contrast Mode discards your palette. The hairline and the semantics are what survive. |
Example usage
1// A light island inside a dark app. Every preview in this Bible uses it.2<div data-theme="light">3 <Card>Renders light regardless of the app theme</Card>4</div>56// Three-way preference: dark, light, or follow the system7type ThemePref = 'dark' | 'light' | 'system'89function resolve(pref: ThemePref): 'dark' | 'light' {10 if (pref !== 'system') return pref11 return matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'12}1314// Follow the OS live, but only while the user is on "system"15useEffect(() => {16 if (pref !== 'system') return17 const mq = matchMedia('(prefers-color-scheme: dark)')18 const on = () => { document.documentElement.dataset.theme = resolve('system') }19 mq.addEventListener('change', on)20 return () => mq.removeEventListener('change', on)21}, [pref])2223// Force light for print, regardless of the current theme24// @media print { :root { color-scheme: light } }CSS
[data-theme='light'] {
color-scheme: light;
/* Every surface is white. The PAGE is what goes grey. */
--ds-canvas: #ffffff;
--ds-sunken: #f4f6f8; /* makes white cards read as raised */
--ds-surface: #ffffff;
--ds-surface-raised: #ffffff;
--ds-surface-overlay: #ffffff;
--ds-surface-inset: #f7f8fa; /* barely off-white, not grey */
/* Not pure black — 15.9:1 is plenty and far more comfortable */
--ds-fg: #171a20;
--ds-fg-secondary: #545c6b;
--ds-fg-muted: #6e7686;
/* Ink alpha, so one border token works on every surface */
--ds-border-subtle: rgb(16 18 22 / 0.07);
--ds-border: rgb(16 18 22 / 0.13);
--ds-layer-hover: rgb(16 18 22 / 0.04);
/* Brand darkens: white text needs 4.5:1 */
--ds-accent: #6a55f2; /* dark theme uses #7c6cff */
--ds-danger-text: #b21c2b; /* the 700 step, for text on a tint */
/* Softer, wider, much lower alpha than the dark shadows */
--ds-shadow-3: 0 4px 8px -4px rgb(16 18 22 / 0.09),
0 12px 24px -6px rgb(16 18 22 / 0.09);
}
/* Printing is always light, whatever the user chose */
@media print {
:root { color-scheme: light; }
.no-print { display: none; }
}Professional tips
- A very slightly grey page is the highest-leverage decision in a light theme. It gives every white card free elevation and costs one token.
- Light mode needs more whitespace than dark mode to feel equally calm — bright surfaces read as louder, so the same density feels busier.
- Check your light theme on a cheap, uncalibrated laptop screen. Subtle greys that look elegant on a good monitor frequently disappear entirely.
- Print styles are light-theme styles with the chrome removed. If the light theme is solid, print is almost free.
Performance
- Light themes consume more power on OLED displays. On a mobile-first product, defaulting to dark is a measurable battery decision.
- Wide, soft shadows are more expensive to paint than tight ones because cost scales with blur². Light mode uses larger blurs, so avoid putting them on dozens of list rows.
- Do not animate the theme transition. A crossfade of every element on the page is one of the most expensive things a browser can be asked to do.
Common mistakes
- Keeping the dark-theme brand colour, so the primary button fails contrast in the theme most people actually use.
- Making the inset grey, which makes every input look disabled.
- Reusing dark shadow values, giving every card a heavy grey smudge instead of a lift.
- Forgetting color-scheme on a light island inside a dark app, so the native scrollbar and caret stay dark inside a white panel.
Real-world recommendations
- Build both themes in the same pull request. A theme added later always ships with a dozen hard-coded colours nobody noticed.
- Screenshot both themes side by side for every major screen and review them together. Half of all theme bugs are only visible in comparison.
- Ask a support team which theme customers report bugs in. It is usually the one the design team does not use daily.
- If you only have budget for one theme, ship light. It is what daylight, printing, screenshots and most documentation assume — but plan the token architecture so dark can arrive without a rewrite.