Noctis

Surface

The layer primitive. A Surface paints a scope base and re-themes everything inside it — set an elevation and the surfaces, borders, and controls within re-derive to that level, so a panel's relationship to its contents holds at every depth.

Elevation

elevation establishes a scope and stamps data-elevationelevated, menu, or sunken — and every role inside re-derives to that level. Omit it for the root canvas. The same panel and the same secondary button repeat at each level below; the level changes, the panel-to-control relationship holds.

root
elevated
menu
sunken

Shade & shadow

shade picks which ramp tier to paint — base (the control-safe default), sunken, surface, or raised — independent of the elevation scope. Pair it with bordered for the standard 1px outline and shadow (card, popover, or modal) to lift the panel off the page.

Sunken well
Raised tier
Card shadow
Popover shadow

Escape hatch

When you can't wrap an element in a <Surface> — a list item, a foreign component's root — spread Surface.props({ shade, bordered, shadow, elevation }) onto it (the D12 escape hatch). It returns a { "data-slot": "noctis-surface", ...dataAttrs } bag the precompiled surface.css keys off, so the element styles as a surface without the wrapper.

  • First row
  • Second row
  • Third row

Anatomy

Surface is a single polymorphic part — no compound subparts. By default it renders a <div>; pass as to change the tag or render to compose it with another component entirely.

  • Surface — the layer element. Props: elevation (the scope it establishes), shade (the ramp tier, default base), bordered (the 1px outline), shadow (the drop-shadow tier, default none), plus as / render for the rendered element.
  • Surface.props(...) — the D12 escape hatch: a spreadable data-slot + data-attribute bag for styling a foreign element as a surface in place.

The rendered element carries data-slot="noctis-surface" for host-side styling and the stable data-surface marker every surface.css rule keys off (present independent of any data-slot override). The scope and look read off data-elevation, data-shade, data-bordered, and data-shadow.

On surfaces

OnSurfaces is the matrix the component pages use to prove a control adapts: it stacks a registered preview down the elevation scopes, each on its own full-width row as a Surface that establishes the scope and paints its base, so a surface-adaptive control re-tunes per layer with no per-component knowledge. Point it at a registry id; the control reads on every layer.

root
elevated
menu
sunken

Design tokens

Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. Surface consumes the neutral ramp roles directly rather than minting its own knobs; retune the surface family by overriding those roles on an ancestor — e.g. .brand { --noctis-color-surface: …; } recolors every card in that region. See Customization for the full override ladder and Tokens for the whole graph.

API reference

Generated from the component's types — every prop, type, default, and description comes straight from the source. Surface is polymorphic, so the native attributes of its rendered tag pass through alongside the layer props; expand a row for the full type and description.

Prop

AttributeDescription
data-slotThe rendered surface element.
data-surfaceThe stable marker every `surface.css` rule keys off — always present, independent of any `data-slot` override, so a composed Surface keeps its shade/border/shadow.
data-elevationThe elevation scope — `elevated` | `menu` | `sunken`; re-themes the subtree.
data-shadeThe ramp tier painted — `base` (default, absent) | `surface` | `raised` | `sunken`.
data-borderedPresent when the standard 1px border is drawn.
data-shadowThe drop-shadow tier — `card` | `popover` | `modal` (absent for none).