Tier System
A semantic vocabulary for border-radius. Three tiers — commit, modify, contain — let components, brand themes, and inline overrides speak the same language without anyone reaching for a raw pixel value.
Three Tiers
Every primitive in the library belongs to exactly one of three tiers. The tier dictates the component's default radius and how it responds to a wrapping context. Brands tune the visual identity by changing the three token values once; component code never touches a raw pixel value.
commit
Pill (9999px). Action, identity, status. Buttons, ButtonGroup, Badge, Toolbar, SegmentGroup, Toggle, Stepper.
modify
Small (~4px). Tap surface, editable. Input, Select, Combobox, Textarea, Checkbox, RadioGroup, Tab, Menu-Item.
contain
Barely-rounded (~2px). Architectural surface, panel. Card, Alert, Dialog, Drawer, Tooltip, Popover.
The four tokens
/* foundation.css — the three tier tokens */
@theme {
--radius-commit: 9999px; /* pill — Button, ButtonGroup, Toolbar */
--radius-modify: var(--radius-sm); /* tap surface — Input, Select, Tab */
--radius-contain: var(--radius-xs); /* container — Card, Alert, Dialog */
--radius-bridge: var(--radius-md); /* middle rung — adjacency + small content surfaces */
}The token names are also the public-API vocabulary: a component reads tier="commit" and applies rounded-commit — the contract is symmetric.
One deliberate exception: the radio indicator reads --radius-control, not the tier's radius. Its circle is
the only thing distinguishing it from a checkbox, so squaring the commit tier for austere
buttons used to square the radios along with them. The token defaults to the same 9999px, so nothing changes until you say so — set --radius-control if you want square radios too. The
checkbox keeps following the tier: a pill-shaped box there is a look you asked for by writing tier="commit", not one a button theme imposed on you.
Tier-aware Components
Seven primitives expose a tier prop AND read from a
wrapping TierContext when the prop is unset. The other
primitives use a fixed tier per component family — see the Blocks index for the per-component
defaults.
| Component | Default tier | Family | Why |
|---|---|---|---|
| Button | commit | Action | Identity declaration — buttons read as decisive. |
| Toggle | commit | Action | Track radius mirrors Button visual weight. |
| SegmentGroup | commit | Navigation | Track + indicator both follow tier for inline action-strip ergonomics. |
| Stepper | commit | Navigation | Indicator + separator are progress-marker affordances. |
| RadioGroup | commit | Form | Indicator + dot read as commit-tier affordances even inside Form-family. |
| Checkbox | modify | Form | Box is the canonical input-tap surface — modify is its native tier. |
| Tab | modify | Navigation | Editorial-leaning navigation surface; commits would feel too loud. |
Prop and context contract
<script>
import { Button, Toolbar } from '@urbicon-ui/blocks';
</script>
<!-- Default: tier="commit" — pill -->
<Button>Launch</Button>
<!-- Explicit: tier="modify" — soft square -->
<Button tier="modify">Launch</Button>
<!-- Wrap in a Toolbar with tier="modify" — children inherit -->
<Toolbar tier="modify">
<Button>Launch</Button> <!-- inherits modify -->
<Button tier="commit">Reset</Button> <!-- explicit override -->
</Toolbar>Context Cascade
A wrapping component with a tier prop publishes that
tier through TierContext. Every tier-aware child that
does not set its own tier prop inherits the wrapping value.
Switch the toolbar tier below to see Button, Toggle, Checkbox, and Badge respond in lockstep.
Children with no tier prop inherit commit. Set tier="commit" on a single child to opt-out of the cascade.
Override Pathway
The three tier tokens are CSS custom properties. Override them at any scope — global @theme for brand identity, a wrapping selector for a
stage-specific look, or an inline style attribute for one-off
experiments. The same Button code re-flows across all three.
--radius-commit: The override sits on the parent <div style> —
every rounded-commit utility inside that subtree picks
up the new value. No prop changes, no component edits.
Brand-level + scoped overrides
/* Brand-level override — applies everywhere */
@theme {
--radius-commit: var(--radius-lg); /* squared pill */
--radius-modify: var(--radius-xs); /* tighter inputs */
}
/* Scoped override — only inside .editorial-stage */
.editorial-stage {
--radius-commit: var(--radius-md);
}Bridge Token
--radius-bridge is the middle rung — var(--radius-md) by default — for the two cases where contain is too hard and commit too soft. The first is adjacency: a floating panel anchored to a commit-tier (pill) trigger is
container-tier content, but its radius wants to pair with the trigger, so a Menu panel under a
pill Button reads as connected rather than as a stranded contain-surface.
The second is optical size: radius scales with the area it turns, so the 2px
edge that reads as precise on a 600px Card reads as a plain rectangle on a ~200px tile. A
small tinted surface is content, not architecture, and takes the middle rung — the ChatMessage bubble, Textarea at tier="commit" (a pill would be absurd on a multi-line
field), and <Card tier="bridge">, which is how a
consumer says "this tile is too small for the container radius" without hand-setting a rounded-* class and splitting the contain family.
Anything that genuinely is a panel, dialog or container stays on contain. Brands tune bridge via the foundation token
like any other tier. See Menu for the adjacency case and Card for the optical one.