All documents

Elevation and depth

Two separate systems, and confusing them is how a menu ends up under a table header: z-index says what is in front, shadow says what is floating. A surface can be one without the other.

The z ladder — every value in the system, and nothing between them

Read out of the components, not invented:

zWhat sits thereWhy it is above the one below
z-0
A card's own click layer (KpiCard's full-surface link)
Below its content, so a hoverable child inside a clickable card still works
z-10
Sidebar panel · pinned table body cell · focused toggle in a group
A pinned column floats over the rows it scrolls past
z-20
The page header (sticky) · plain sticky table header · sidebar drag strip
A sticky header must beat the rows under it
z-30
Pinned table header cell (sticky on both axes)
It must beat both the pinned column (10) and the plain header (20)
z-40
A full-surface "updating" overlay pinned over a panel (presets)
Above the content it covers, below every portalled overlay
z-50
Dialog · Sheet · Dropdown menu · Select content · Popover
Every portalled overlay shares one layer; they never stack against each other
z-[60]
The map's hover chip
Above the map's floating chrome, below the overlays
z-[70]
The feedback aim ring
Above every screen it is drawn on, below the pill that explains the mode
z-[71]
The feedback mode pill
The one element that says the mode is armed. It must never be the thing that is covered
z-[100]
Tooltip (and its arrow)
Deliberate headroom over a table's own sticky max of 30 — a tooltip on a pinned header must not vanish under it

Do not invent a value between these. The gaps are the headroom: they exist so a new sticky thing can slot in without a re-shuffle of everything above it.

Two rules that fall out of the ladder:

  • The pinned-header rules are mutually exclusive by construction. A pinned head cell takes a plain z-30; writing a descendant rule as well would put a (0,2,0) container rule against a (0,1,0) utility and the pin would lose.
  • A pinned cell is bg-inherit, so it keeps its row's zebra, hover and selected colour while it floats. A pinned cell with its own fill is a stripe that disagrees with its row.

Elevation — three tiers, and a resting surface has none

TierTokenValueWhat earns it
Resting
--shadow-card
0 1px 2px rgba(0,0,0,.04), 0 1px 6px rgba(0,0,0,.04)
A card. Barely there — the border is what defines it
Overlay
--shadow-card-hovershadow-overlay
0 4px 12px rgba(0,0,0,.06), 0 12px 32px rgba(0,0,0,.06)
Popover, dropdown, select content, tooltip, floating map panel
Modal
--shadow-modal
0 24px 80px rgba(0,0,0,.18)
Dialog, Sheet

In dark mode both overlay and modal resolve to --shadow-modal: a soft shadow is invisible on charcoal, so the raised surface leans on the titanium ramp (--surface-card a step lighter than the page) and takes the deeper shadow to keep an edge.

Rules:

  • A resting surface is a LINE, not a shadow. Cards sit on a 1px border. If you find yourself adding a shadow to make something "pop", the answer is usually contrast or spacing.
  • Shadows are soft and warm — pure black at 4–18% alpha. Never a grey-blue shadow, never a hard offset.
  • Elevation is not a hover state. A card that lifts on hover is a card that moves; the row ladder and the accent tint carry hover in this system.
  • Reach them as shadow-[var(--shadow-card)], or the minted shadow-overlay / shadow-modal utilities. --shadow-card: var(--shadow-card) inside @theme is a self-reference that resolves to nothing (11-traps.md).

Shadow used as a BORDER — the inset family

Six of these exist and they are not elevation at all; they are hairlines that must not take part in layout:

UtilityWhereWhy a shadow and not a border
shadow-[inset_-1px_0_0_var(--border)]
pinned-left table column
A real border would shift the cell's box and break column alignment while it floats
shadow-[inset_1px_0_0_var(--border)]
pinned-right column
same
shadow-[inset_3px_0_0_var(--foreground)]
selected table row
A 3px left bar with zero layout cost, and the second cue selection needs at 1.28:1
shadow-[inset_0_0_0_1px_var(--foreground)]
focus/selection ring inside a dense control
A ring that lives inside the box rather than outside it
shadow-[inset_0_0_0_1px_var(--destructive)]
the Bug half of the feedback form’s type toggle
The same ring one hue over: selected still reads as selected, and the destructive tone says which half you picked without a second control
shadow-[0_0_0_1px_var(--sidebar-border)]shadow-[0_0_0_1px_var(--sidebar-accent)] on hover
the rail's outline button
A 1px "border" that can change colour on hover without a reflow

shadow-none is also a real value: the line tab variant switches its active state off the enclosed pill's shadow.

Layering surfaces without shadow

The system prefers a step in the surface ramp to a shadow:

  • light: page cream #FAF8F5 → card warm white #F5F3EF → muted #F1EEE8
  • dark: charcoal #18181B → titanium #2C2C2E → titanium mid #3A3A3C → edges #48484A

Three steps is the whole depth budget of a screen. If you need a fourth, the screen is doing too much.