Radius
A short semantic scale, primitive-specific aliases for nesting, and a global multiplier.
Most radius systems are a scale. Zazz adds a primitive layer so nested corners look right.
Radius works in three layers: a short semantic scale (none, xs through xl, full), primitive-specific aliases (button, input, badge, card) that point to specific scale steps, and a global multiplier that scales everything at once.
The semantic layer covers most direct decisions. The primitive layer solves the nesting problem (buttons inside cards, badges on buttons) without designers having to do the math each time.
Semantic radii
The base scale. Five named radii plus none and full.
--radius-none: 0rem;
--radius-xs: calc(var(--step-1) * var(--radius-multiplier)); /* 0.25rem (4px) */
--radius-sm: calc(var(--step-1_5) * var(--radius-multiplier)); /* 0.375rem (6px) */
--radius-md: calc(var(--step-2) * var(--radius-multiplier)); /* 0.5rem (8px) */
--radius-lg: calc(var(--step-4) * var(--radius-multiplier)); /* 1rem (16px) */
--radius-xl: calc(var(--step-7) * var(--radius-multiplier)); /* 1.75rem (28px) */
--radius-full: 9999rem;Each numbered radius derives from a step on the spacing scale, multiplied by --radius-multiplier. The scale is intentionally short. Five rungs cover most surfaces, and --radius-full handles capsules and pills.
--radius-none exists so a component can switch off its rounding without resorting to a literal 0. Tokens stay tokens; nothing breaks the convention.
Primitive radii
Frequently-used primitives have dedicated radius tokens that point to a semantic step.
--radius-button: var(--radius-md); /* 0.5rem (8px) */
--radius-input: var(--radius-md); /* 0.5rem (8px) */
--radius-badge: var(--radius-full); /* capsule */
--radius-card: var(--radius-lg); /* 1rem (16px) */The reason: nested radii look broken when the inner element's radius is too close to the container's. A button inside a card with the same radius reads as not-quite-aligned to the eye. By assigning button and card different semantic steps (--radius-md and --radius-lg), the math comes out right by default.
Primitive tokens also let you retune one primitive group without disturbing the rest. Make every button a capsule:
:root {
--radius-button: var(--radius-full);
}Card corners, input rounding, and badge shapes stay where they are.
Global multiplier
--radius-multiplier is the lever for sharpening or rounding every radius at once.
--radius-multiplier: 1;Default is 1. Bump it for rounder corners across the product; drop it for a sharper, more architectural feel. Components don't care. They consume tokens that resolve through the multiplier.
.architectural {
--radius-multiplier: 0; /* sharp corners everywhere */
}
.friendly {
--radius-multiplier: 1.5; /* rounder, softer */
}--radius-md at multiplier 1.5 becomes step-2 × 1.5 = 0.75rem. The proportions between xs, sm, md, lg, and xl stay constant regardless of multiplier. They all scale together.
--radius-none and --radius-full are unaffected by the multiplier. none is always 0. full is always capsule.
Nested radius math
The classic problem: a button (8px radius) sits inside a card (16px radius). If the button's radius is too close to the card's, the visual rhythm breaks. Zazz's primitive defaults ship with the math worked out:
| Primitive | Token | Default | Resolves to |
|---|---|---|---|
| Card | --radius-card | --radius-lg | 16px |
| Button | --radius-button | --radius-md | 8px |
| Input | --radius-input | --radius-md | 8px |
| Badge | --radius-badge | --radius-full | capsule |
A card at 16px with a button inside at 8px reads cleanly. The 2:1 ratio gives the eye enough difference. A badge on a button stays capsule regardless of context, dodging the nesting math entirely.
The defaults compose well. When you retune primitives for a project, check the most-common nesting paths in your UI and verify the inner radius stays visibly smaller than the outer.
The strict geometric rule: the inner radius should equal the outer radius minus the inner element's distance from the outer edge. If a card has a 16px radius and 16px of internal padding, an element flush against the card's inner edge would need a 0px radius for the curves to be concentric.
Most of the time you don't want elements pressed flush. They sit on a stack with some gap, so the math softens. A good rule of thumb: the inner radius should be roughly half the outer, and never larger than two-thirds.
Zazz's primitive defaults follow this:
--radius-card(16px) →--radius-button(8px). Half.--radius-card(16px) →--radius-input(8px). Half.--radius-button(8px) →--radius-badge(capsule). Full sidesteps the math.
When you retune the primitives, pick steps that preserve this kind of ratio. A button at --radius-lg inside a card at --radius-md looks visibly wrong; a button at --radius-md inside a card at --radius-xl reads clean.