Skip to main content

Design tokens

@ambientcss/components builds every control's sizing, radius, and spacing from a small set of shared tokens (the --ambx-* layer in styles.css), rather than each component picking its own numbers. This page is the reference for that scale. It's separate from the physically-grounded lighting/shadow API documented in @ambientcss/css → Classes — that --amb-* vocabulary is frozen and grounded in measured renders; these --ambx-* tokens are the layout layer built on top of it.

Grid

Every value on this page is a multiple of one base unit:

--ambx-grid: 4px;
--ambx-grid-half: calc(var(--ambx-grid) / 2); /* 2px */
--ambx-grid-quarter: calc(var(--ambx-grid) / 4); /* 1px */

Border radius

TokenValue
--ambx-radius-sm4px
--ambx-radius-md8px
--ambx-radius-lg12px
--ambx-radius-xl16px
--ambx-radius-full9999px

These are deliberately identical in px to @ambientcss/css's .amb-rounded / -md / -lg / -xl / -full utilities (see Classes → Border Radius) — the two layers sharing one scale is intentional cross-layer coherence, not a coincidence. Radius does not vary with a component's size — only footprint (min-width/height, padding, font) scales; solving the concentric formula below at three different sizes would force off-scale radii, which defeats the point of a canonical scale.

Concentric shapes

When one rounded shape sits inside another (a button's cap inside its clearance well), the outer radius is always the inner radius plus the clearance between them — expressed as a formula, never as an unrelated second number:

  • Pill button: cap radius is --ambx-radius-lg (12px, on-scale); the well radius is calc(var(--ambx-radius-lg) + var(--ambx-grid-half)) (14px — the well's own clearance padding added on top).
  • Square button: well radius is --ambx-radius-md (8px, on-scale); the cap radius is calc(var(--ambx-radius-md) - var(--ambx-grid-half)) (6px — the same clearance subtracted, solved the other way because here the well sits on the scale instead of the cap).

Sizes

size="sm" | "md" | "lg" is available on AmbientButton, AmbientKnob, AmbientSlider, AmbientFader, and AmbientSwitch (Switch had this first; the others were added to match it). "md" reproduces each component's original, unchanged footprint — adding size to existing code is non-breaking.

Componentsmmd (unchanged)lg
Knob (diameter)48px64px80px
Slider (thumb / track)20px / 6px24px / 8px32px / 12px
Fader (thumb w×h / track)20×30px / 6px24×36px / 8px32×48px / 12px
Button, pill (min-width)64px72px80px
Button, round (min-width)40px48px56px
Button, square (min-width)48px56px64px
Switch (w×h)40×20px48×24px64×32px

Track/thumb length for slider and fader (120px) is layout-driven, not size-driven, and stays fixed across sizes.

AmbientPanel has no size prop — see its own docs for why.

Thickness and elevation don't scale with size

--amb-thickness and --amb-elevation (the physically-grounded shadow depth, see Classes → Elevation) are absolute physical measurements — a real 9mm-tall knob doesn't get taller just because its diameter grows — so they're deliberately not scaled by size. A lg control keeps the same absolute shadow depth as md/sm, which reads as comparatively shallower on lg and deeper on sm. This matches how the same physical part would actually look at a bigger or smaller diameter — it's an intentional characteristic of the grounding, not a bug.

Spacing

Composing several controls into one device face borrows a hardware-panel convention (Braun/Dieter Rams, Teenage Engineering): placement follows one modular pitch. Tighter gaps read as "these controls belong to one function"; looser gaps read as "these are separate zones"; the panel's own edge margin is drawn from the same vocabulary so its border reads as one more slot in the lattice, not an afterthought.

--ambx-gap-tight: 12px;   /* one functional unit / a repeated row */
--ambx-gap-normal: 20px; /* a few related-but-distinct controls */
--ambx-gap-loose: 32px; /* separating zones, or the panel edge */

Each tier is roughly 1.6–1.7× the last, so they stay visually distinct at a glance rather than blurring together.

Component (md)TightNormalWhen to use tight vs. normal
Knob (64px)12px ≈ 0.19×20px ≈ 0.31×Tight: a dense secondary knob bank. Normal: a primary/frequently-adjusted knob, or one next to unrelated controls.
Button (48–72px)12px20pxTight: a repeated key/transport row. Normal: a single button among mixed controls.
Switch (48px)12px20pxTight: a bank of switches. Normal: one switch with its own label, isolated.
Fader/Slider (track 8px, thumb 24px)12px20pxTight: a mixer-style fader bank. Normal: a single fader isolated among other control types.
Panel edge marginAlways loose (32px) when the panel is a device face — see ambx-panel-device below.

These are fixed absolute tokens, not size-aware — same philosophy as the radius scale. When composing lg-sized controls, step up one tier from what you'd use for md (use loose where normal would apply); step down one tier for sm.

Rack

.ambx-rack (and the AmbientRack convenience component) applies one of these gap tiers as a flex container:

import { AmbientRack, AmbientKnob, AmbientButton } from "@ambientcss/components";

function TransportRow() {
return (
<AmbientRack gap="tight">
<AmbientButton shape="round">Rec</AmbientButton>
<AmbientButton shape="round">Play</AmbientButton>
<AmbientButton shape="round">Stop</AmbientButton>
</AmbientRack>
);
}
PropTypeDefault
gap"tight" | "normal" | "loose""normal"
direction"row" | "column""row"

Also accepts standard HTMLAttributes<HTMLDivElement> props.

Device-face panels

AmbientPanel's default padding (16px) is unchanged — it's a general container (cards, settings panels) used far beyond device mockups, so its default shouldn't jump to the much larger loose-gap value. Add the ambx-panel-device class when a panel is a device face composed of one or more AmbientRacks, so its edge margin equals the loose gap used between them:

<AmbientPanel className="ambx-panel-device">
<AmbientRack gap="loose">
<AmbientRack gap="tight">{/* a knob bank */}</AmbientRack>
<AmbientRack gap="tight">{/* a key row */}</AmbientRack>
</AmbientRack>
</AmbientPanel>

Mirrored in ambient3d

The same three gap constants are mirrored in ambient3d/components/_common.py (GAP_TIGHT_MM / GAP_NORMAL_MM / GAP_LOOSE_MM, plus a row_layout/grouped_row_layout helper), kept in lockstep by design — the same convention amb_model.py's lighting defaults already use to mirror ambient.css's :root block (see Grounded). ambient3d's own demo device mockup (components/panel.py) composes its grille, knob bank, fader/button/switch row, and transport/number keys using these same tiers, so the Blender referents demonstrate the same hardware-composition pitch as the CSS/React layer, not just the same lighting.