Depth & z-axis model

mandatory

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.

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 (webdesktopmobile/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:

Depth Meaning 2D projection XR projection
0 On the page plane no shadow flush with volume back-plane
1 Just lifted --shadow-1 + tonal ~0.5 cm forward
2 Raised --shadow-2 ~1.0 cm
3 Floating --shadow-3 ~1.5 cm
4 Hovering above content --shadow-4 ~2.0 cm
5 Top 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:

Band Families Depth
Base page background, content 0
Raised cards, sticky headers, app bars 1–2
Floating FAB, contextual toolbars 3
Overlay menus, dropdowns, tooltips, snackbars 3–4
Modal dialogs, sheets, drawers + their scrim 5
System toasts/critical alerts, command palette 5 (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).

<div data-kds-3d-demo"depth-parallax" data-aria-label"Interactive depth demo: move the pointer to see surfaces at different depth levels shift by amount inverse to their depth. Static under reduced-motion.">/div

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