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 margin——Always 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.