Skip to content

Depth & z-axis model

themes specs/themes/depth.kmd

The cross-surface z-axis model. Defines depth as ONE abstract scale with two projections: 2D (rendered as shadow + tonal lift via `elevation.kmd`) and spatial (rendered as literal centimetres via `xr-preview.kmd`). Owns the stacking / z-order contract and the depth→projection mapping so a surface picks a depth level once and renders coherently on web, desktop, mobile, TV and XR. Material parity: generalizes M3 elevation into a surface-agnostic axis.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Depth & z-axis model

Facet Visual do Koder Design. Material parity: generalizes https://m3.material.io/styles/elevation from a 2D shadow scale into a surface-agnostic depth axis.

Koder Design has one depth axis. A surface picks a depth level once; each surface technology then projects that level:

  • 2D (web/desktop/mobile/TV) → shadow + tonal lift — see elevation.kmd.
  • Spatial (XR) → literal forward offset in centimetres — see xr-preview.kmd.

This spec owns the axis and its projection contract; elevation.kmd owns the 2D shadow recipes; xr-preview.kmd owns the spatial placement. Pick depth here, render there.

R1 — The depth scale

6 levels (0–5), shared with elevation by construction:

DepthMeaning2D projectionXR projection
0On the page planeno shadowflush with volume back-plane
1Just lifted--shadow-1 + tonal~0.5 cm forward
2Raised--shadow-2~1.0 cm
3Floating--shadow-3~1.5 cm
4Hovering above content--shadow-4~2.0 cm
5Top of the stack (modal)--shadow-5~2.5 cm

Canonical mapping: 1 depth level ≈ 0.5 cm of forward offset in XR (xr-preview.kmd § R3). One token, --kds-depth-level, carries the integer; renderers read it.

R2 — Depth is relative to the nearest stacking context

A depth level is local to its container, not a global z-index. A level-3 menu inside a level-5 dialog is still "above" its dialog parent — depth composes within a stacking context, it doesn't reset to absolute screen z.

Rule (from elevation.kmd § R5): a child's depth must be ≥ its parent's. A surface that needs to escape its parent's stacking context (e.g. a tooltip over a clipped scroll area) is portaled to a higher context, then takes its depth there.

R3 — Canonical z-order (stacking bands)

Overlapping surface families occupy fixed bands so a popover never loses to a sticky header by accident:

BandFamiliesDepth
Basepage background, content0
Raisedcards, sticky headers, app bars1–2
FloatingFAB, contextual toolbars3
Overlaymenus, dropdowns, tooltips, snackbars3–4
Modaldialogs, sheets, drawers + their scrim5
Systemtoasts/critical alerts, command palette5 (above scrim)

CSS z-index is allocated in matching ranges via tokens (--kds-z-raised, --kds-z-overlay, --kds-z-modal, --kds-z-system) — never hand-pick a z-index integer.

R4 — Parallax & perceived depth (2D)

On flat surfaces, depth can also be implied by motion (pointer/gyro parallax, depth-on-scroll). That is a perceptual cue, not a real layer:

  • Parallax magnitude SHOULD scale with depth level (deeper = moves less, like distant scenery).
  • Perceived-depth motion lives in themes/motion/spatial.kmd and is always gated by prefers-reduced-motion (freezes to the static layout).
  • Parallax MUST NOT reorder the real stacking bands (R3).

R5 — Forbidden patterns

  • ❌ Raw z-index integers in widget code (use band tokens, R3).
  • ❌ Child surface at lower depth than its parent (R2).
  • ❌ Re-deriving cm offsets per component instead of from the level (R1 owns the 0.5 cm mapping).
  • ❌ Using depth purely decoratively — depth implies the lighting model casts a shadow (lighting.kmd); flat decoration is depth 0.
  • ❌ Parallax that changes which surface is "on top" (R4).

R6 — Accessibility

  • Depth is never the only signal (mirror elevation.kmd § R7): pair with border, position, or tonal change.
  • forced-colors: active → projections collapse; depth shown by border weight (elevation.kmd § R6).
  • Reduced-motion freezes all perceived-depth motion (R4); the static stacking order (R3) is unaffected.

T1-T3 — Tests

T1 — Mapping: for every depth level, the 2D projection resolves the matching --shadow-N and the documented XR cm offset (level × 0.5).

T2 — No raw z-index: koder-spec-audit depth --zindex finds zero hard-coded z-index integers outside the band-token definitions.

T3 — Parent ≥ child: render-tree audit finds no surface whose depth is below its stacking-context parent.

  • themes/elevation.kmd — 2D projection (shadow + tonal)
  • themes/lighting.kmd — the rig that makes depth cast shadow
  • develop/xr-preview.kmd — spatial projection (centimetres)
  • themes/motion/spatial.kmd — perceived-depth motion (parallax)
  • app-layout/safe-area.kmd — XR volume bounds the z-axis lives in

References