Border Radius in Design Systems: Tokens, Consistency, and Scale
Why Radius Tokens Matter
A design system without radius tokens inevitably suffers from inconsistent rounding. One developer uses 8px, another uses 10px, and somewhere a designer specified 0.5rem. The result is a UI where buttons, cards, inputs, and modals each feel slightly different — a subtle but persistent sense that something is off.
Radius tokens solve this by constraining choice to a curated set of values, enforced through CSS custom properties and design tool configurations.
Defining a Radius Scale
Most design systems need 4–6 radius levels. Here is a proven scale:
| Token | Value | Use Case |
|---|---|---|
--radius-none | 0 | Tooltips, tight-fitting overlays |
--radius-sm | 4px | Small elements: badges, tags, checkboxes |
--radius-md | 8px | Inputs, buttons, chips |
--radius-lg | 12px | Cards, modals, dropdowns |
--radius-xl | 16px | Feature cards, image containers |
--radius-full | 9999px | Avatars, pills, circular buttons |
:root {
--radius-none: 0;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-full: 9999px;
}
The jump between levels should be noticeable. If --radius-sm is 6px and --radius-md is 8px, the visual difference is too small to justify separate tokens.
Semantic Naming
Avoid naming tokens after their values (--radius-8px) — rename every token when the scale changes. Instead, use semantic names that describe the element size or context:
/* ✅ Semantic — survives value changes */
.button { border-radius: var(--radius-md); }
.card { border-radius: var(--radius-lg); }
/* ❌ Literal — tightly coupled to current values */
.button { border-radius: var(--radius-8px); }
.card { border-radius: var(--radius-12px); }
For teams that prefer contextual naming over size-based naming:
--radius-button: var(--radius-md);
--radius-card: var(--radius-lg);
--radius-input: var(--radius-md);
--radius-avatar: var(--radius-full);
This adds a layer of indirection but makes intent clearer and allows per-component overrides.
Scaling Strategy
Linear Scale
Each step increases by the same amount:
0, 4px, 8px, 12px, 16px, 20px
Simple and predictable. Works well for utilitarian interfaces where subtlety is not needed.
Non-Linear Scale
Larger steps at higher levels create visual emphasis:
0, 2px, 4px, 8px, 16px, 9999px
This mirrors how people perceive size differences — a 2px change matters more at small sizes than at large ones.
Which to Choose
- Linear for enterprise/internal tools where consistency matters more than personality
- Non-linear for consumer-facing products where visual hierarchy drives engagement
Responsive Radius
On small screens, large radii eat into content area. Scale down radii on mobile:
:root {
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
}
@media (max-width: 640px) {
:root {
--radius-lg: 8px;
--radius-xl: 12px;
}
}
This preserves the token hierarchy while reducing the visual weight of rounded corners on constrained viewports.
Dark Mode Considerations
Rounded corners can create visual ambiguity in dark mode when two dark elements overlap without visible borders. Ensure sufficient contrast between nested rounded elements:
/* Light mode: subtle border provides separation */
.card {
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
}
/* Dark mode: increase border visibility */
:root.dark .card {
border-color: var(--color-border-strong);
}
Integration with Component Libraries
When building components that accept a radius prop, map to tokens rather than accepting arbitrary values:
<script setup lang="ts">
const props = defineProps<{
radius?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'
}>()
</script>
<template>
<div :class="`rounded-${props.radius ?? 'md'}`">
<slot />
</div>
</template>
With Tailwind CSS, use the configured radius tokens in tailwind.config.ts:
export default defineConfig({
theme: {
borderRadius: {
none: '0',
sm: 'var(--radius-sm)',
md: 'var(--radius-md)',
lg: 'var(--radius-lg)',
xl: 'var(--radius-xl)',
full: '9999px',
},
},
})
Key Takeaways
- Define 4–6 radius tokens to constrain choice and enforce consistency
- Use semantic naming (
--radius-sm) over literal naming (--radius-8px) - Linear scales suit utilitarian UIs; non-linear scales add visual personality
- Reduce radii on mobile to preserve content area
- Dark mode may need stronger borders at rounded intersections
- Components should accept token names, not arbitrary pixel values
Try It Yourself
Preview border-radius values across different element sizes with our Border Radius Generator. Test individual corners, copy CSS custom property definitions, and experiment with responsive adjustments.
Related Guides
- Border Radius Guide — complete syntax and individual corner control
- Complex CSS Shapes — pushing border-radius into organic and geometric forms