Should dark mode be pure black?
Almost never, and it is worth seeing why in numbers rather than as a rule of thumb. Here are the dark backgrounds and card surfaces from five unrelated shipping palettes, in HSL:
| Background | Card | Neutral hue | |
|---|---|---|---|
| Warm industrial | 30 8% 10% | 24 7% 14% | Warm orange-brown |
| Neutral product | 0 0% 10% | 0 0% 15% | True grey |
| Archival blue | 222 31% 11% | 222 34% 15% | Deep blue |
| Quiet green | 128 40% 8% | 137 26% 11% | Forest green |
| Earth dashboard | 36 10% 10% | 36 7% 14% | Warm sand |
Three patterns, all consistent, none of them accidental.
- Nothing is at 0% lightness. The darkest is 8%. Pure black is the one value with no room beneath it — the moment you need a surface *below* the background, you have nowhere to go.
- Cards are lighter than the background, by 3 to 5 points. In dark interfaces, raised means lighter. This inverts the light-mode instinct, where a card is often the lighter thing sitting on a tinted page.
- Only one of the five is true grey. The rest carry a hue — 30, 222, 128, 36 — usually pulled toward the accent. A tinted dark neutral reads as designed; an untinted one reads as a default, because it is the default.
The one case for near-black
OLED screens genuinely draw less power at #000000, and a media or photography interface can want the frame to disappear. That is a deliberate trade against having an elevation range, and it works when the content supplies the contrast. It is not a general-purpose default, and it is not what most people mean when they reach for black.
Dark is not an inversion
The fastest way to a bad dark mode is to flip the lightness of every light-mode value. Four specific things break, in roughly this order:
- Shadows stop working. A shadow is darkness cast on a lighter surface. On a dark surface there is nothing left to darken, so every elevation cue you had silently disappears and the interface flattens.
- The accent fails contrast. A saturated mid-tone brand colour that reads cleanly on white is often too dark against a near-black background. Inverting the neutrals does not touch it, so it fails quietly rather than obviously.
- Borders vanish or shout. A light-mode border is a slightly darker line. Inverted, it becomes a slightly lighter line — which either disappears against the card or glows against the background, depending on which surface it lands on.
- Disabled and muted states collapse. In light mode these are lighter greys. Inverted, they become dark greys on a dark background, and "muted" turns into "invisible".
A dark palette is a second design, not a filter applied to the first.
Elevation without shadow
Since shadows are unavailable, dark interfaces express depth through surface lightness. That is what the background-to-card gap in the table above is doing: each step up in the stack is a step up in lightness.
You need at least three levels, and it is worth naming them rather than improvising: the page background, a raised surface (cards, panels), and an overlay surface (popovers, dropdowns, dialogs) that sits above both. Spacing them 3 to 5 lightness points apart keeps the boundaries readable without turning the screen into stripes. Below about 3 points the levels stop being distinguishable at normal brightness; much above 6 and the top surface starts looking like a light-mode panel someone dropped in.
Surface vs tonal surface
Some systems distinguish a neutral raised surface from a *tonal* one that mixes a little of the brand hue into the elevation. Both are legitimate. The tonal version makes the whole interface feel deliberately coloured rather than grey-with-an-accent; the neutral version keeps colour meaning for things that need it. Pick one and apply it everywhere — mixing them is what makes a dark theme look like two themes.
Accents have to move
Look again at the primaries from those same five kits: 12 71% 46%, 46 85% 60%, 225 63% 80%, 114 47% 50%, 23 92% 60%. Every one is a light or mid tone. None is a deep saturated version of the hue, because a deep saturated colour on a near-black surface is both hard to read and hard to look at.
The general rule is that a light-mode accent has to lighten and usually desaturate for dark mode. Lightening buys contrast; desaturating stops it vibrating against the dark neutral, which is the effect that makes some dark themes physically tiring. And the accent needs its own foreground decision: a colour bright enough to read on dark is often bright enough to need *dark* text on top of it, which is the reverse of the light-mode pairing.
Check both directions against a contrast target. Body text on the background is the obvious one; the two that get missed are muted text on a card, and the accent's foreground on the accent itself.
Five swatches is not a palette
This is where most palette galleries leave you. A row of five hexes tells you the mood and answers almost none of the questions a real screen asks: what colour is a border on a card, what does muted text look like on an overlay, what does a destructive button look like when it is disabled, what colour is the ring on a focused input.
Token specimen · real values
Ambient Sage
Live renderAmbient Sage's actual tokens — the same values its exports use.
Color tokens
Ambient Sage
Core
background
H 84 · C7, 0, 18, 89
foreground
H 68 · C0, 0, 4, 11
card
H 80 · C5, 0, 16, 85
muted
H 77 · C5, 0, 16, 83
border
H 73 · C4, 0, 16, 78
Brand
primary
H 53 · C0, 8, 68, 0
primary-fg
H 84 · C7, 0, 18, 89
secondary
H 75 · C4, 0, 16, 81
accent
H 52 · C0, 8, 60, 3
ring
H 53 · C0, 8, 68, 0
Semantic
destructive
H 0 · C0, 75, 75, 20
destructive-fg
H 0 · C0, 0, 0, 0
success
H 123 · C45, 0, 43, 27
warning
H 36 · C0, 35, 85, 0
muted-fg
H 0 · C0, 0, 0, 0
Charts
chart-1
H 53 · C0, 8, 68, 0
chart-2
H 207 · C59, 26, 0, 4
chart-3
H 123 · C35, 0, 34, 22
chart-4
H 340 · C0, 41, 27, 4
chart-5
H 36 · C0, 20, 50, 0
The role list is the same in both modes, which is the point: background, foreground, card, card-foreground, popover, popover-foreground, muted, muted-foreground, primary, primary-foreground, secondary, accent, destructive, border, input, ring, plus chart series. Define them once per mode and every component resolves without anyone picking. Skip them and each new screen re-picks, differently. The full list and what each role is for is in semantic colour tokens explained.
Ambient Sage
Live renderRendered from the kit's actual tokens, fonts, and treatments
Dashboard
Welcome back — here's how Ambient Sage is performing today.
Active users
15.1k
+5%Trending up this month
vs. previous 30 days
MRR
$49.1k
+3%Strong recurring growth
Net of churn
Retention
89%
+2%Engagement above target
Rolling 28-day window
NPS
69
+3Meets growth projections
Survey · n=1,204
Total revenue
Last 12 months
$49.1k+18.2%
Recent sales
You closed 265 deals this month.
Alex Rivera
alex@ambientsage.com
Mira Okonkwo
mira@ambientsage.com
Jonas Feld
jonas@ambientsage.com
Sana Qureshi
sana@ambientsage.com
Theo Lindgren
theo@ambientsage.com
Recent transactions
View all| Customer | Status | Date | Amount |
|---|---|---|---|
AR Alex Rivera Founder & CEO | Paid | 2m ago | $1,999.00 |
MO Mira Okonkwo Head of Product | Pending | 1h ago | $39.00 |
JF Jonas Feld Design Lead | Processing | 3h ago | $299.00 |
SQ Sana Qureshi Engineering Lead | Paid | Yesterday | $99.00 |
TL Theo Lindgren Brand Director | Refunded | 2d ago | $2,400.00 |
Typography
Plus Jakarta Sans
Color system
28 semantic roles, light + dark
Agent outputs
DESIGN.md, CSS, Tailwind, shadcn
Four complete dark palettes, and what separates them
Everything above is a rule. These are four dark sets that follow it, chosen because they resolve the same problem in visibly different ways. Read the background and card pair in each: that gap is the elevation model, and it is the decision that most determines whether a dark UI reads as designed or as inverted.
Token specimen · real values
Files Archival
Live renderFiles Archival's actual tokens — the same values its exports use.
Color tokens
Files Archival
Core
background
H 0 · C0, 0, 0, 90
foreground
H 26 · C0, 2, 3, 2
card
H 0 · C0, 0, 0, 86
muted
H 0 · C0, 0, 0, 84
border
H 0 · C0, 0, 0, 80
Brand
primary
H 225 · C67, 50, 0, 12
primary-fg
H 0 · C0, 0, 0, 0
secondary
H 350 · C0, 30, 25, 76
accent
H 47 · C0, 11, 50, 4
ring
H 225 · C67, 50, 0, 12
Semantic
destructive
H 5 · C0, 73, 79, 21
destructive-fg
H 0 · C0, 0, 0, 0
success
H 146 · C63, 0, 35, 52
warning
H 39 · C0, 31, 88, 17
muted-fg
H 20 · C0, 8, 12, 40
Charts
chart-1
H 225 · C67, 50, 0, 12
chart-2
H 351 · C0, 32, 27, 11
chart-3
H 47 · C0, 11, 50, 4
chart-4
H 6 · C0, 64, 71, 12
chart-5
H 15 · C0, 10, 13, 18
Token specimen · real values
The Social Club
Live renderThe Social Club's actual tokens — the same values its exports use.
Color tokens
The Social Club
Core
background
H 52 · C0, 4, 33, 82
foreground
H 45 · C0, 2, 7, 4
card
H 54 · C0, 3, 32, 76
muted
H 54 · C0, 3, 32, 76
border
H 65 · C2, 0, 28, 69
Brand
primary
H 61 · C1, 0, 66, 15
primary-fg
H 55 · C0, 8, 100, 90
secondary
H 52 · C0, 4, 32, 55
accent
H 61 · C1, 0, 66, 15
ring
H 61 · C1, 0, 66, 15
Semantic
destructive
H 9 · C0, 62, 72, 39
destructive-fg
H 0 · C0, 0, 0, 0
success
H 119 · C60, 0, 61, 56
warning
H 35 · C0, 38, 89, 23
muted-fg
H 52 · C0, 2, 11, 23
Charts
chart-1
H 61 · C1, 0, 66, 15
chart-2
H 9 · C0, 62, 72, 39
chart-3
H 185 · C33, 3, 0, 61
chart-4
H 119 · C60, 0, 61, 56
chart-5
H 24 · C0, 38, 63, 42
How to read four palettes quickly
Ignore the accent first; it is the part you will remember and the part that matters least structurally. Compare background against card to see the elevation model, then muted-foreground against card to see whether secondary text survives on a raised surface. Those two comparisons predict most of how the palette will behave in a real screen.
Why this got urgent
Interfaces are increasingly built by coding agents working from a written brief, and dark mode is the single most commonly missing thing in those briefs. We sampled 299 public DESIGN.md files — the file a coding agent reads before writing UI — and measured what they cover.
| Share of files | |
|---|---|
| No dark mode at all | 69% |
| No semantic colour roles — raw hex only | 86% |
| No prohibitions, only preferences | 76% |
The consequence is predictable. Asked for a dark variant with nothing written down, an agent inverts the greys — the exact move that breaks all four things listed earlier. It produces something plausible on the first screen and inconsistent by the fifth, and because each individual screen looks fine, nobody catches it until the whole app is built.
A ten-minute check
- 1
Stack three surfaces
Put an overlay on a card on the background, with a one-pixel border on each. If any boundary disappears, your lightness steps are too close or your border role is doing double duty.
- 2
Render muted text on every surface
Not just on the background. Muted-on-card is where an inverted palette usually falls apart, and it is the most common text state in a dense interface.
- 3
Build a destructive confirmation
It exercises destructive, its foreground, its hover and its border together — the cluster of roles people define last and use first.
- 4
Focus something
Tab to an input. A focus ring is a colour nobody picks deliberately, so it is either your accent doing a job it is bad at, or a browser default nobody chose.
- 5
Look at a dense table
Three greys next to each other is the hardest thing a dark palette does. A palette that survives a settings table survives a marketing page trivially; the reverse is not true.
Common mistakes
- Starting at pure black. It removes the level below your background before you have discovered you need one.
- Reusing the light-mode accent unchanged. It usually needs to lighten, often needs to desaturate, and may need dark text on top of it.
- Keeping shadows and adding lightness steps. Pick one elevation language. Doing both makes raised surfaces read as smudged.
- Defining only what the first screen needed. The roles you skip are the ones the next screen invents, and it will invent them differently.
- Testing on a marketing page. Hero sections flatter every palette. Test on the densest screen you have.
Browse complete dark palettes, not swatch rows
Every kit here defines all its colour roles in both light and dark, plus type, radii and motion — installable as one shadcn registry item. Free kits need no account.
FAQ
Should I use pure black for dark mode?
Generally no. Across five shipping palettes the darkest background is 8% HSL lightness and none is 0%. Pure black leaves no surface below the background, so you lose the ability to show anything recessed. The exception is an OLED-power or media-viewing context where the frame is meant to disappear.
Is dark mode just inverting the light palette?
No. Four things break: shadows stop working because there is nothing left to darken, the accent usually fails contrast, borders either vanish or glow depending on which surface they land on, and muted and disabled states become invisible. A dark palette is a second design, not a filter.
How do I show elevation in dark mode without shadows?
With surface lightness. Define at least three levels — background, raised surface, overlay — spaced roughly 3 to 5 HSL lightness points apart. Below about 3 points the levels stop being distinguishable; much above 6 and the top surface starts to look like a light-mode panel.
What is the difference between a surface and a tonal surface?
A neutral surface raises lightness only; a tonal surface also mixes in some of the brand hue as it rises. Tonal makes the whole interface feel deliberately coloured, neutral keeps colour meaning available for things that need it. Both work — mixing them within one theme does not.
Do my brand colours work in dark mode?
Usually not unchanged. A saturated mid-tone that reads well on white tends to be too dark and too vibrant against a near-black surface. Lighten it for contrast, desaturate it slightly so it stops vibrating, and check whether it now needs dark rather than light text on top.
How many colours does a dark palette need?
About 28 named roles, not five swatches: background and foreground, card, popover, muted, primary, secondary, accent, destructive and their foregrounds, plus border, input, ring and chart series. Anything you leave undefined gets invented per screen, differently each time.