Learning Track
Filter content by your role to see the most relevant sections
Radius & Shape Foundations
Give your interface a consistent visual personality: a stepped radius scale, semantic shape roles for surfaces and controls, the pill-vs-regular contract, and how shape interacts with density, focus, and brand identity.
Why This Matters
Shape is the foundation users perceive fastest and describe worst. Nobody articulates "the corner radii are inconsistent"; they say the product feels off, or cheap, or like two teams built it. A 4px input inside a 12px card on an 8px modal is exactly that—three shape languages in one screen, each defensible, together incoherent.
Radius is also brand infrastructure. Sharp corners read technical and serious; generous curves read friendly and consumer. When shape lives in tokens, that personality dial is a mapping change—swap the semantic roles onto different scale steps and the whole product moves together. When shape lives in per-component CSS, rebranding means archaeology.
This page covers the radius system as built here: the shape.radius scale in the core tokens, the semantic shape roles above it, and the control-level pill contract.
Core Concepts: The Scale, the Roles, the Contracts
An Eight-Step Scale with a Named Middle
The core scale in ui/designTokens/core/shape.tokens.json:
// shape.radius (values)
none: 0px 01: 2px 02: 4px medium: 6px
03: 8px 04: 16px 05: 32px full: 9999pxThe climb is roughly doubling with intent: hairline (2) for chips and tags, control (4) for inputs and buttons, the named medium (6) as the workhorse control corner, up through 8 for cards and 16 for large surfaces, with full as the pill. Note that medium sits between 02 and 03 numerically—a named token inserted where the scale had a gap. It is a wart worth knowing: the scale's numbering is historical, the semantic names are the interface.
Semantic Roles Are the Interface
Consumers never touch the numbered steps. The semantic layer maps them to roles:
// ui/designTokens/semantic/shape.tokens.json (excerpt)
"shape": {
"radius": {
"extraSmall": "{shape.radius.01}", /* 2px */
"small": "{shape.radius.02}", /* 4px */
"default": "{shape.radius.02}", /* 4px */
"medium": "{shape.radius.03}", /* 8px */
"large": "{shape.radius.04}", /* 16px */
"extraLarge": "{shape.radius.05}" /* 32px */
},
"control": {
"radius": {
"default": "{shape.radius.medium}", /* 6px */
"pill": "{shape.radius.full}" /* 9999px */
}
}
}Read the two mappings as two different questions. Surface roles (small…extraLarge) answer "how rounded is this container?" and scale with the container—a badge takes extraSmall, a card takes medium or large. Control roles answer "how rounded is this interactive element?" and hold constant across sizes: every button is control.radius.default (6px) regardless of its width, which is why a row of differently-sized controls still reads as one family.
The Pill Contract
full: 9999px exists for one shape: the pill, where corner radius equals half the height. Expressed as a huge fixed value, it always equals half of any height up to ~20000px—the browser clamps. The contract is that pills are a deliberate variant (control.radius.pill) for filter chips, toggle tracks, and avatar frames—not a slider you tune. A 12px-radius button that "looks sort of pill" is drift; it is either 6 or it is a pill.
Shape Interacts With Everything
- Focus rings: the focus outline follows the element's radius—the semantic
control.border.focusWidth(the 2pxthickwidth) pairs with the same corner value, so focused pills get pill-shaped rings for free. - Elevation: shadows take the element's radius; a 16px card with an 8px shadow curve reads as mismatched weight.
- Density: tight-density layouts keep the same radii—shape is personality, not space, and rescaling it with density muddies both systems.
- Nesting: inner corners step down one role from their container (a 16px modal hosts 8px cards hosts 6px controls). Equal or ascending inner radii look like alignment errors.
System Roles: Where Shape Decisions Land
Design Impact
Designers own the personality dial—which semantic roles map to which steps—and the nesting ladder. The review question is "which role is this corner?" with the same discipline as color and spacing: a radius that cannot name its role is a radius the system cannot change.
Engineering Impact
Engineers own consumption hygiene: scoped component tokens resolving semantic roles (the Card contract ships --ds-card-size-radius-medium resolving a semantic shape value with a literal fallback), and the rule that radii never appear as literals in rules.
Brand Impact
Brand owns the extremes. A rebrand toward sharpness moves the semantic mapping down-scale in one file; toward softness, up. That is the entire cost of a shape rebrand when shape is tokenized—and a cross-codebase audit when it is not.
Design & Code Interplay
In the design tool, shape tokens appear as corner-radius variables with the same role names, applied through component styles rather than per-frame tweaking. The comp-side contract that matters most: nested containers visibly step their radii down, and pills are drawn as true pills—not as approximately-round rectangles the implementer must guess about.
Because radius is brand infrastructure, the design library keeps one shape mode per brand—same structure as the color brand layers—so previewing a softer brand is a mode switch, not a re-draw.
In code, semantic roles emit as custom properties and components bind them through scoped contracts:
/* Generated + component contract */
@layer core {
:root {
--core-shape-radius-04: 16px;
--core-shape-radius-full: 9999px;
}
}
[data-ds-component='Card'] {
--ds-card-size-radius-medium:
var(--semantic-shape-radius-large, 16px);
}
.card { border-radius: var(--ds-card-size-radius-medium); }
/* Controls hold constant shape across sizes */
.button { border-radius: var(--semantic-control-radius-default, 6px); }
.chip { border-radius: var(--semantic-control-radius-pill, 9999px); }
/* Focus follows shape automatically */
.button:focus-visible {
outline: var(--semantic-control-border-focus-width, 2px) solid
var(--semantic-color-border-focus);
border-radius: inherit;
}Applied Example: A Filter Bar, Shaped Coherently
Ship a shape-heavy pattern: a filter bar with pill chips inside a card, above a data table.
- Assign by role, top-down: the card is a surface →
radius.mediumsemantic (8px). The chips inside are controls and pills →control.radius.pill. The table below is sharp-cornered data →radius.nonewith hairline borders from the border system. - Check the ladder: card 8 → chips pill (the exception that reads correctly because pills are absolute), and no nested element equals its container's radius.
- Interactions keep their shape: chip removal animates opacity and translate tokens, never the corner—a radius that animates reads as morphing, and morphing is motion-system vocabulary, not a filter chip's job.
- Focus shapes follow: tabbing through the chips shows pill-shaped focus rings (2px
focusWidth) because the outline inherits the radius. - Brand check: switching to a softer brand moves the semantic mapping; the bar's relationships survive because everything consumed roles.
Five checks, zero new numbers. The bar looks like the rest of the product because it is the rest of the product, shape-wise.
Constraints & Trade-offs
- Fixed steps vs optical radius: huge surfaces technically want proportionally larger radii to look equal; a stepped scale accepts slight optical variance for predictability. Where it matters (hero cards), pick the next role—do not compute per-element.
- One control radius vs per-variant: holding 6px for all controls costs expressiveness (no extra-round secondary buttons) and buys the strongest version of "controls are one family."
- 9999px trick vs explicit 50%: the huge-value pill cannot express elliptical or asymmetric corners; those rare cases fall to dedicated components, not the scale.
- Shape vs density decoupling: keeping radii constant across density modes preserves identity but means dense layouts carry rounded corners into tight spaces—the accepted cost, revisited only if clipping artifacts appear.
Common Pitfalls & Failure Modes
1. Literal radii
/* Bad */
.input { border-radius: 4px; }
/* Good */
.input { border-radius: var(--semantic-control-radius-default, 6px); }2. Nesting without stepping
/* Bad: Card 8px hosting an 8px panel */
.panel { border-radius: var(--semantic-shape-radius-medium); }
/* Good: Step down inside */
.panel { border-radius: var(--semantic-shape-radius-small); }3. Almost-pills
/* Bad: Hand-tuned fake pill */
.tag { border-radius: 14px; }
/* Good: Commit or don't */
.tag { border-radius: var(--semantic-control-radius-pill); }4. Scaling radius with size
Corner radius that grows with an element's width (5% of width, say) destroys the constant-control contract and makes sibling controls disagree. Shape is a role, not a ratio.
Shape System Health Metrics
Signal 1: Role coverage
- Healthy: every
border-radiusin product CSS resolves through a semantic role (or scoped contract); the numbered steps appear only inside the token sources. - Warning: a handful of literal radii survive from a pre-token era—each an orphan the next rebrand cannot reach.
- Critical: literals are the norm; a shape rebrand would be a find-and-replace across the codebase, which is the failure the layering exists to prevent.
Signal 2: Ladder integrity
- Healthy: nested corners step down one role from their container everywhere; a spot check of any modal shows panel → card → control in descending order.
- Warning: equal or ascending inner corners appear on one surface—reading as an alignment error users cannot name.
- Critical: no ladder survives inspection; nesting looks accidental product-wide.
Signal 3: Control constancy
- Healthy: all controls resolve
control.radius.default(6px) or the deliberate pill, at every size. - Warning: one control family picked its own corner—sibling controls now disagree subtly on every screen they share.
- Critical: per-variant radii are common; the "one family" promise is gone.
Migration Strategy: Collecting the Corners
- Inventory the literals: every
border-radiusvalue with its surface. Dedupe; most products discover five or six distinct numbers serving three roles. - Assign roles top-down: each surface gets its semantic role (container scale with size, controls constant, pills deliberate); literals snap to the role's step with nearest-step tolerance.
- Fix the ladder violations: nesting that equalled or ascended gets its step-down; the visual change is small and the "designed" reading is immediate.
- Sweep and lint: the border-radius literal pattern joins the per-directory lint as areas clean, keeping the rebrand cost at one mapping file.
Real-World Case Studies
Case 1: The rebrand that took an afternoon
A sharper brand direction moved three semantic mappings down-scale in one file, rebuilt, and shipped—every surface, control, and pill moved together because nothing consumed a literal. The team that had resisted the layering became its advocates at the sight of a rebrand reviewed as a generated diff.
Case 2: The almost-pill that fooled everyone
A 28px-tall chip with border-radius: 14px looked like a pill until focus arrived—the ring inherited a true-radius sibling and the mismatch exposed the fake. Committing to control.radius.pill fixed shape and ring in one token.
Case 3: The card inside the card
A media card hosted an inner panel at the same 8px corner; users read the inner edge as a rendering glitch. The step-down to small (4px) made the nesting read as structure—the ladder rule earning its keep on the least glamorous surface in the product.
Verification Checklist
Additional Resources
- Borders & Strokes — the lines that pair with these corners (
/blueprints/foundations/borders) - Elevation & Shadows — shadows follow shape (
/blueprints/foundations/elevation) - The sources —
ui/designTokens/core/shape.tokens.json,ui/designTokens/semantic/shape.tokens.json
Related Concepts
Reflection Questions
Apply This Concept
A brand refresh asks for a "friendlier" feel. Map exactly which semantic radius roles move, what stays fixed, and why controls should resist the change more than surfaces.
Reflect
You find a component using border-radius: 14px on a 28px-tall chip. Is this a pill expressed badly, or a new shape need? What evidence decides, and what is the repair in each case?
