Skip to content

Elevation

themes specs/themes/elevation.kmd

Elevation system — 6 levels (0-5) expressed as shadows + tonal surface colors. Material parity (`/styles/elevation`). Defines WHEN to use which level (per element family) and HOW to render it (shadow + tonal overlay per theme mode).

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Elevation

Facet Visual do Koder Design. Material parity: https://m3.material.io/styles/elevation.

The 6 elevation levels (0-5)

LevelShadow recipeTonal overlay (dark mode)Use
0nonenone (surface = bg)Page background, flat elements
10 1px 2px rgba(0,0,0,0.10), 0 2px 6px rgba(0,0,0,0.06)5% accent over surfaceDefault card, list item
20 1px 3px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.08)8% accentRaised card, app bar (scrolled)
30 4px 8px rgba(0,0,0,0.14), 0 8px 16px rgba(0,0,0,0.10)11% accentFAB (default), elevated button
40 6px 10px rgba(0,0,0,0.16), 0 12px 24px rgba(0,0,0,0.12)12% accentFAB hover, contextual menu
50 8px 16px rgba(0,0,0,0.18), 0 16px 32px rgba(0,0,0,0.14)14% accentModal sheet, dialog, dropdown

Token names: --shadow-1 through --shadow-5, --tonal-1 through --tonal-5.

R1 — Element family default elevation

FamilyDefault level
Surface (page bg)0
Container (card)1
Container (sheet)3
Control (button — filled)0 (matches surface)
Control (button — elevated)1
Control (button — outlined/text)0
Control (FAB)3
Modal/dialog5
Snackbar4
Tooltip3
Menu4
Dropdown4
Drawer (overlay)5
Bottom sheet (scrim attached)5

R2 — Light vs dark elevation

Light mode: shadow-driven

Shadow visible because surfaces are bright; tonal overlay barely contributes (0-3% accent maximum).

:root {
  --shadow-1: 0 1px 2px rgba(0,0,0,0.10), 0 2px 6px rgba(0,0,0,0.06);
  --tonal-1: rgba(59, 91, 253, 0.00); /* effectively none */
}
.card { box-shadow: var(--shadow-1); background: var(--surface); }

Dark mode: tonal-driven

Shadows are mostly invisible on dark backgrounds; tonal overlay of the accent color shifts the surface toward "lighter / closer" to communicate elevation. Material 3 default.

[data-theme="dark"] {
  --shadow-1: 0 1px 2px rgba(0,0,0,0.40), 0 4px 12px rgba(0,0,0,0.30);
  --tonal-1: rgba(126, 151, 255, 0.05); /* 5% accent */
}
.card {
  box-shadow: var(--shadow-1);
  background:
    linear-gradient(var(--tonal-1), var(--tonal-1)),
    var(--surface);
}

The linear-gradient trick lets the tonal overlay compose on top of the surface color without nesting.

R3 — State-driven elevation changes

Per interaction/states.kmd:

StateElevation change
Hover (mouse)+1 level (card 1 → 2)
Focus (keyboard)No elevation change (focus uses ring)
Press (touch)-1 level (FAB 3 → 2, "pushed in")
Drag+2 levels (lifts)
Dropped/settledback to default

Animation: transition: box-shadow var(--motion-fast) var(--ease-standard);

R4 — Per-preset variation

Presets vary in "depth" feel:

PresetShadow scale
verge (default)Standard (Adwaita-based v0)
material3Standard
material2Slightly stronger shadows
gnomeSubtle shadows; tonal-heavy in dark
windows_11Mica blur replaces shadow at level 3+
windows_95NO shadows — flat 3D bevel borders instead
macos_sonomaSoft, longer shadows; iOS-like depth
flat_design (planned)Level 0 for everything
brutalistNO shadows
neumorphismINVERSE shadows (inset for "pressed in" feel)
glassmorphismCombines shadow + backdrop blur
material_expressiveLarger shadows; +1 level baseline
terminal_classicNO shadows
carbon_ibmNO shadows (flat by design)

Per-preset details in ui-style.kmd.

R5 — Forbidden combinations

  • ❌ Level 5 element inside a level 3 element (parent must be ≥ child)
  • ❌ Animating elevation > 1 level on hover (jarring)
  • ❌ Using box-shadow for purely decorative reasons (use level 0)
  • ❌ Hardcoded shadow values in widget code (always token)
  • ❌ Different shadow colors per element (use the system — single color, varying opacity)

R6 — Forced colors / high contrast

When OS reports forced-colors: active or prefers-contrast: more:

  • All shadows → none (forced-colors mode strips shadows anyway)
  • Tonal overlay → none
  • Elevation indicated by border instead: 1px solid border for each level above 0

R7 — Accessibility

  • Shadow MUST NOT be the only indicator of elevation/state
  • Always pair shadow with: border color shift, position offset, or background tonal change
  • Color-blind users should perceive elevation via tonal lightness change, not shadow alone
  • themes/color-roles.kmd — accent color used for tonal overlay
  • themes/light-dark.kmd — light/dark mode switches shadow/tonal balance
  • themes/ui-style.kmd — per-preset elevation defaults
  • interaction/states.kmd — state-driven elevation changes
  • foundations/elements.kmd — family ↔ default elevation

R8 — Recesso / sunken (design-RFC-012)

Eixo ortogonal à elevação. A elevação responde "quão acima da página?" (recursiva, tonal — no dark mais alto = mais claro). O sunken responde "esta superfície é um recesso escavado na sua moldura?" — profundidade 1, não-recursivo por contrato.

Token: --surface-sunken (verge.kmd §R4.1/§R4.3) — dark #0A0A0B, light #ECEEF0.

  • R8.1 — --surface-sunken é sempre ≥1 passo mais escuro que a moldura no dark (cinza-inset no light); nunca mais claro que o container.
  • R8.2 — Não-recursivo: proibido como container de filhos elevados (cards/menus/dialogs). Quem precisa de filhos elevados usa a rampa 0–5.
  • R8.3 — Recesso define-se por tom + borda (hairline --border), não por sombra (um recesso não "flutua"); sombra inset opcional como cue.
  • R8.4 — a11y: texto sobre --surface-sunken mantém AA; separação tonal recesso↔moldura ≥1.15× (themes/light-dark.kmd).
  • Usar em: telas de auth emolduradas (id#190), input wells, blocos de código, painéis inset. Não usar onde superfícies empilham acima da página → elevação.

❌ Anti-pattern: usar --surface-sunken e depois pôr um card/menu elevado dentro dele (quebra o eixo; o recesso não é base de empilhamento).

References