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

R9 — Cues de sombra inset direcional (stack#403, gov.br parity)

R8.3 define o recesso por tom + borda e cita "sombra inset opcional como cue". Este conjunto tokeniza essa cue — a sombra inset complementa, nunca substitui, o par tom+borda do recesso (R8.3). gov.br expõe inset shadows direcionais; o KDS os adota como cue de estado, não como mecanismo de recesso.

Tokens (assets/css/base.css, exportados no dist KDS — 4 arestas × 4 tamanhos): --shadow-inset-{top,right,bottom,left}-{sm,md,lg,xl}. A aresta nomeia o lado onde a sombra encosta (top = conteúdo "empurrado pra dentro" pelo topo); o tamanho escala offset+blur (sm 1/2 · md 2/4 · lg 4/8 · xl 6/12 px).

R9.1 — Contrato light vs dark

Como toda a rampa (R2), a inset é theme-aware: no light um preto sutil (α 0.10→0.16 por tamanho); no dark a mesma geometria com α mais forte (0.35→0.50) — um inset preto quase some sobre superfície escura, então a aresta recessada precisa de mais contraste pra ler. Os offsets não mudam entre temas.

R9.2 — Quando usar

  • Estados pressed/active — um botão/toggle "afundado" ao pressionar (o inverso do -1 nível de R3 pra press em elementos que não flutuam): a aresta superior ganha --shadow-inset-top-sm.
  • Input wells / campos recessados — como cue adicional ao --surface-sunken
    • hairline (R8), quando o desenho pede reforço direcional.
  • Trilhos/canais (sliders, progress tracks) — o sulco por onde o thumb corre.

R9.3 — Proibido

  • ❌ Usar inset como único sinal de recesso (R8.3: tom+borda são o mecanismo; a inset é cue). Um recesso definido só por sombra some pra quem tem sombras off.
  • ❌ Compor inset com uma sombra de elevação (drop) no mesmo elemento — recesso e elevação são eixos opostos (R8); um elemento é uma coisa ou outra.
  • ❌ Inventar valores fora da escala sm/md/lg/xl.

References