Elevation and states
Layers are opaque steps of a surface scale. Hover and pressed are a wash laid over whatever is beneath. Each is measured with the instrument that can see it.
This page answers a question that came up on 2026-09-12: in dark mode, are panels, elevated surfaces and hovers done with white alphas or with a colour scale? The colour scales looked too strong. Eleven reference systems were read from the packages they publish, and they agree on a pattern the values below follow.
See it
The ladder in the theme you are reading in: the canvas, a card on it, a well inside the card, and a panel above them all. Flip the theme with the toggle in the sidebar and watch what does the separating — the shadow in light, the colour step in dark.
surface/raised — a card
Choosing a level
base is the canvas. raised is anything that sits on it and stays — a card, the sidebar, a table’s rows. overlay is anything that comes and goes above the page — a menu, a popover, a dialog — and it is the last step: a popover inside a dialog stays on overlay and takes a border, rather than inventing a level. sunken is a well inside a card — a read-only field, the fill of a checkbox — and on the canvas it takes a border, because in dark the two are the same colour.
A shadow means elevation and a border means structure. In light, raised and overlay are both white and the shadow is the whole difference; in dark the shadow reaches 1.05:1 against the canvas and the colour step does the work, with a hairline where a surface has run out of steps. Hover and pressed are never a level: they are the wash, laid over whatever is beneath, so a hovered row does not rise.
The ladder
Three levels in each mode. In light the shadow separates raised from overlay; in dark a shadow stops reading as height and the colour step does the work, so overlay is a lighter step. The dark steps are ΔL .043 in OKLCH, where the reference systems that read as calm place theirs — .025 to .045 — and where this system used to jump a whole stop, .085. 925 is the surface step added for this; it is aliased only here and in the stone ladder a neutral product would use instead.
| Level | light | ΔL from below | WCAG from below | |
|---|---|---|---|---|
overlay Modals, popovers, dropdowns | white #FFFFFF | +0.000 | 1.00:1 | |
raised Cards, panels, table body | white #FFFFFF | +0.025 | 1.07:1 | |
base App canvas | stone/050 #F5F7F9 | — | — | |
| Level | dark | ΔL from below | WCAG from below | |
|---|---|---|---|---|
overlay Modals, popovers, dropdowns | night/900 #1B1E38 | +0.043 | 1.11:1 | |
raised Cards, panels, table body | night/925 #12142C | +0.043 | 1.08:1 | |
base App canvas | night/950 #090B1F | — | — | |
surface/sunken shares the canvas in dark and sits ΔL +0.043 below a card. When a component runs out of levels — a popover inside a dialog — it stays on overlay and takes a border.
Why lightness, not the ratio
The WCAG ratio adds 0.05 to both luminances, which flattens the dark end: Radix’s first two dark greys, #111111 and #191919, are 1.06:1 apart and everyone sees the step. The step from the canvas to a card here is 1.08:1. The suite used to hold the ladder to 1.09 and would have refused it while passing the .085 jump that looked wrong. Text on a surface and a boundary on a surface stay on the ratio, which is what WCAG asks of them; surface against surface — a ladder step, a well in a card, a wash — is measured in OKLCH lightness, with a floor of .035.
Shadows
Two steps, md for anchored panels and lg for the Dialog, and each is two layers: a long soft one and a short contact one, at the drawn offsets. The geometry is shared between modes; only the ink changes. Light takes the ink alphas of the drawing. Dark was never drawn, and it is modest on purpose: black at 64% over the dark canvas would reach 1.05:1 against it while black at 8% over white reaches 1.19, so dark does not chase a shadow that cannot work and stops at 32 and 48%. The last column is the darkest layer flattened over the ground it falls on.
| Step | Layers | Light ink | Dark ink | Darkest point, light · dark |
|---|---|---|---|---|
md | 10 / 32 / -4 · 6 / 14 / -6 | ink-10, ink-12 | black-32, black-48 | 1.29:1 · 1.04:1 |
lg | 14 / 64 / -4 · 8 / 22 / -6 | ink-12, ink-12 | black-32, black-48 | 1.29:1 · 1.04:1 |
The wash
interactive/wash-hover and wash-pressed are mist/500 at 8 and 16% in light, 12 and 20% in dark. One ink: it darkens a light surface with the faint cyan cast the light hover always had, and lightens a dark one. A control with no fill of its own — a row, a menu item, a ghost or outline button — sets its background to the wash; a control with a fill — the neutral button, the calendar’s month buttons, a dialog’s icon buttons — keeps it and lays the wash over it as a background image. The text beneath keeps its own token, and the tightest pair where rows and menu items live, text/tertiary, is read in the last column of each cell.
| Over | wash-hover | wash-pressed | ||||
|---|---|---|---|---|---|---|
base #F5F7F9 | #EAEFF1 ΔL −0.026 | tertiary text 4.64AA | #DFE7E8 ΔL −0.053 | tertiary text 4.283:1 | ||
raised #FFFFFF | #F3F6F6 ΔL −0.029 | tertiary text 4.95AA | #E7EDED ΔL −0.058 | tertiary text 4.54AA | ||
overlay #FFFFFF | #F3F6F6 ΔL −0.029 | tertiary text 4.95AA | #E7EDED ΔL −0.058 | tertiary text 4.54AA | ||
sunken #EAEDF1 | #E0E6E9 ΔL −0.024 | tertiary text 4.273:1 | #D6DEE1 ΔL −0.050 | tertiary text 3.943:1 | ||
| Over | wash-hover | wash-pressed | ||||
|---|---|---|---|---|---|---|
base #090B1F | #151B2C ΔL +0.064 | tertiary text 7.17AAA | #1C2635 ΔL +0.106 | tertiary text 6.37AA | ||
raised #12142C | #1D2338 ΔL +0.057 | tertiary text 6.50AA | #242D40 ΔL +0.094 | tertiary text 5.76AA | ||
overlay #1B1E38 | #242C42 ΔL +0.050 | tertiary text 5.80AA | #2B3549 ΔL +0.083 | tertiary text 5.14AA | ||
sunken #090B1F | #151B2C ΔL +0.064 | tertiary text 7.17AAA | #1C2635 ΔL +0.106 | tertiary text 6.37AA | ||
Every text token clears AA under both washes on base, raised and overlay in both modes, and under hover on sunken. Tertiary clears both on raised and overlay, where rows and menu items sit; on the canvas it clears hover and misses pressed by 0.22, and on a well it misses both. Nothing puts helper text on a washed control over those two surfaces, and the suite records the figures so that changes. The neutral button’s label under the pressed wash is 12.97AAA light and 8.86AAA dark.
The menu keeps its own rule: the accent and danger rows hover to their own subtle surfaces. The neutral row takes the wash, on which the accent label would now be 7.86:1 in dark.
Borders
border/subtle is an alpha in both modes — stone/950 at 8% in light, white at 16% in dark — so it reads on every surface without picking a stop above any of them. The opaque stone/100 it replaced was the light sunken surface itself, invisible there; the night/700 it replaced in dark was 1.97:1 on a card, twice the weight the references draw. default and strong stay opaque: one is a recorded exception, the other is the 3:1 every form control relies on.
| border/subtle over | Light | Dark | ||
|---|---|---|---|---|
base | alpha/ink-08 → #E2E4E7 · 1.19:1 | alpha/white-16 → #303243 · 1.54:1 | ||
raised | alpha/ink-08 → #EBECEC · 1.18:1 | alpha/white-16 → #383A4E · 1.62:1 | ||
overlay | alpha/ink-08 → #EBECEC · 1.18:1 | alpha/white-16 → #3F4258 · 1.65:1 | ||
sunken | alpha/ink-08 → #D8DBDF · 1.18:1 | alpha/white-16 → #303243 · 1.54:1 | ||
What the references do
Ten of the eleven, read from each system’s published package or stylesheet, not from its documentation prose. Layers are opaque in every one; transient states are alphas of a mid grey or a tinted near-white in most; borders are moving to alpha. Material 3 dropped Material 2’s white overlay per elevation for opaque tonal roles.
| System | Layers | Hover, pressed, selected | Borders |
|---|---|---|---|
GitHub Primer | opaque — inset #010409, default #0d1117, muted #151b23 | #656c76 at 20% hover, 25% active, on every transparent control | default opaque; muted is default at 70% |
Atlassian | opaque — surface #242528, raised #2B2C2F, overlay #303134; ΔL .025–.029 | #E3E4F2 at 12% hover, #E5E9F6 at 25% pressed | input border is #E3E4F2 at 12% |
Radix Colors / Themes | opaque gray 1–5, ΔL .028–.039; a translucent panel exists and requires backdrop blur | gray-a3 hover, gray-a4 active; the alpha scale is built so a3 over gray1 equals gray3 | steps 6–8 of the opaque scale |
Adobe Spectrum | opaque — base #111, layer-1 #1b1b1b, layer-2 #222; ΔL .024–.044 | opaque scale; a transparent-white scale (11–21%) is reserved for content over images | opaque |
IBM Carbon g100 | opaque — #161616, #262626, #393939, #525252; ΔL .068–.094 | layer-hover opaque #333; background-hover rgba(141,141,141,.16), selected .24 | opaque |
Vercel Geist | opaque — bg L 4%, gray 100–400 at L 10 / 12 / 16 / 18% | a separate gray-alpha scale: white at 6, 9, 13, 14, 24% in dark, black in light | gray 400–600 |
shadcn/ui v4 | opaque — bg L .145, card .205, muted .269, accent .371 | accent opaque | white at 10%; input at 15% |
Apple iOS | opaque — #000, #1C1C1E, #2C2C2E, one step higher when elevated | systemFill rgba(120,120,128,.36) dark, .20 light — same ink, two alphas | separator #545458 at 60% |
Discord | opaque — #1e1f22, #2b2d31, #313338 | rgba(78,80,88,.3) hover, .6 selected | white at 6% |
Material 3 | opaque tonal roles: surface-container lowest…highest at N4 / N10 / N12 / N17 / N22 | state layers: the content colour at a fixed opacity | — |
What it cost
The dialog moved a step closer to its scrim: the darkest ink at 95% over the canvas is now 1.19:1 below it, where it was 1.43. Nothing darker than the ink exists and a lighter scrim moves toward the dialog, so the edge is the border’s, as it is for the menu — border/default is 3.55:1 against the scrim — with the shadow as reinforcement. The suite records the figure. Two token names left the theme, interactive/neutral-hover and neutral-pressed, and every dark surface value changed; the package went to 0.2.0.
Accessibility
A surface step is a step for the eye, not a contrast requirement: no text is ever set in one surface against another, which is why the ladder is measured in lightness and text stays on the ratio. What the wash changes is the ground under text, so every text token is re-measured over both washes on every surface, and the tables above are those readings. Hover is never the only cue for anything: a row under the wash is the same row, and what it does is said by its content, not by its lift. The focus ring is drawn on every level and clears 3:1 on the highest, 3.923:1 in light and 9.23AAA in dark.