Skip to content

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.

Live preview

Everything below is the real component. Change the controls, tab through it, and turn on Inspector Mode to read any value off the screen.

Light theme

Invoice 2026-0431

Due 26 July · $1,428.00

Unpaid

Automatic payment is enabled

We will charge Visa ···4242 on the due date.

Depth without lightness

Four elevation levels on a single white surface. Remove the shadows and the hierarchy disappears completely — the opposite of dark mode.

e1
e2
e3
e5

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.

Dark-theme values on white — too light, white text fails

brand-500success-500warning-400danger-500

Light-theme values — darker, white text passes

brand-600success-600warning-500danger-600

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.

#F7F8FA — barely off-white
#E4E7EC — inverted dark logic

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.

Canvas#FFFFFF
Sunken#F4F6F8
Inset#F7F8FA
Raisedwhite + e2
Overlaywhite + e5
Hoverblack 4%
Borderblack 13%
Scrimink 42%

Anatomy

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.

Inset, barely off-white

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.

  1. 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.

  2. 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.

  3. 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.

  4. Inset#F7F8FA

    Barely off-white. Going darker to mimic the dark theme produces a grey box that reads as disabled.

  5. 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.

Design tokens used

Values are read live from the running stylesheet, so this table can never drift from the code. Click any value to copy it.

Color

TokenValueUsed for
--ds-canvasPage background
--ds-sunkenPage behind cards — makes white read as elevated
--ds-surfaceCards, panels, dialogs — all the same white
--ds-surface-insetInputs, code blocks, table headers
--ds-fgPrimary text — 15.9:1
--ds-fg-secondaryBody text — 7.4:1
--ds-fg-mutedCaptions — 4.9:1
--ds-border-subtleHairlines — the primary edge definition
--ds-accentBrand — one step darker than in dark mode
--ds-danger-textError text on a pale tint — the 700 step
--ds-layer-scrimBehind modals — lighter than the dark-mode scrim

Shadow

TokenValueUsed for
--shadow-e1 … e5—Softer and wider than their dark counterparts

Do

white on #F4F6F8
Put white cards on a grey pageA card that is lighter than its background reads as elevated before any shadow is applied. It is the light-theme equivalent of the dark-theme surface ramp.
5.7:1 ✓3.3:1 ✗
Darken brand and status coloursWhite text needs 4.5:1. Most brand colours tuned for dark backgrounds land around 3:1 on white, which is a straightforward failure on the primary button.
shadow only
+ hairline
Lean on hairlinesOn white, a shadow alone gives a soft, undefined edge. A 7% ink hairline is what makes a card look crisp, and it is the only thing that survives High Contrast Mode.
danger-700 on a 10% tint — passesdanger-500 on a 10% tint — fails
Use the 700 step for text on tintsA pale 10% tint plus a 500-step text colour lands around 3:1. The 700 step is the pair that clears 4.5:1, which is why -text exists as its own token.

Don't

Inverted from dark — every surface is now mid-grey.

Do not invert the dark themeInversion gives you a grey page, grey cards, a washed-out brand colour and shadows that are far too heavy. The two themes share names, not values.
Do not reuse dark-mode shadowsA 60%-alpha shadow on white is a dirty smudge, not depth. Light shadows are 6–16% alpha and spread much further.
Do not make the inset greyA #E4E7EC input reads as disabled. In light mode the field boundary comes from the border, not from a darker fill.

Pure black body text over several paragraphs is harsher than it looks in a single line.

Do not use pure black text#000 on #FFF is 21:1 and produces the same halation problem in reverse. #171A20 is 15.9:1 and noticeably more comfortable over a long document.

Accessibility

Not a checklist to run at the end. These are the requirements the component was built from.

1.4.3Contrast (Minimum)AA1.4.11Non-text ContrastAA1.4.6Contrast (Enhanced)AAA

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 → themeThe 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.
AttributeApplied toNotes
color-scheme: light[data-theme="light"]Keeps native controls, scrollbars and autofill consistent when a light island sits inside a dark app.
prefers-color-schemeMedia querySets the initial value only. An explicit user choice must win and must persist.
forced-colorsMedia queryHigh Contrast Mode discards your palette. The hairline and the semantics are what survive.

Code

Example usage

tsx
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

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; }
}

Notes

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.