Shape
Shape system — corner-radius scale, when to use which radius, and per-preset variation rules. Material parity (`/styles/shape/overview-principles`). Every UI element binds its corner radius to a role from this scale; per-preset overrides come from `ui-style.kmd`.
Spec — Shape
Facet *isual*do Koder Design. Material parity: https://m3.material.io/styles/shape/overview-principles.
The shape scale (5 levels)
| Role | Default radius (px) | Use |
|---|---|---|
radius-none |
0 | Sharp corners — Bauhaus, brutalist, Windows 95, Carbon |
radius-xs |
4 | Chip, badge, small button, tag |
radius-sm |
8 | Default button, input field, tab, segmented control |
radius-md |
12 | Card, dialog, sheet, container |
radius-lg |
16 | Modal sheet, full-screen dialog, hero container |
radius-xl |
24 | Floating action button, large sheet edge, ornament |
radius-full |
999 | Pill button, avatar, chip with rounded ends |
Defaults shown above match verge (Adwaita-based v0). Each preset in ui-style.kmd overrides these.
R1 — Role binding
Every container/control binds radius via a role, not a raw px:
button { border-radius: 8px; }
button { border-radius: var(--radius-sm); }// ❌
BorderRadius.circular(8.0)
// ✅
BorderRadius.circular(KoderShape.of(context).sm)R2 — Per-element default
Element family → typical role binding:
| Family | Default | Notes |
|---|---|---|
| Surface (page bg) | none | Pages don't have corners |
| Container (card) | radius-md |
Standard card frame |
| Container (sheet) | radius-lg on edges that meet screen, radius-none elsewhere |
|
| Control (button) | radius-sm |
Most common control radius |
| Control (FAB) | radius-xl or radius-full |
Pill/round |
| Control (chip) | radius-xs or radius-full |
Per preset |
| Control (input) | radius-sm |
Match buttons |
| Decoration (badge) | radius-full |
Always pill |
| Content (image) | radius-md |
Optional; depends on context |
| Modal/dialog | radius-lg |
Distinct from regular cards |
R3 — Perpreset variation (from uistyle.kmd)
The 35 ratified presets each define their own scale. Sample:
| Preset | xs | sm | md | lg | xl |
|---|---|---|---|---|---|
verge (default) |
2 | 4 | 6 | 8 | 16 |
material3 |
4 | 8 | 12 | 16 | 24 |
material2 |
4 | 4 | 4 | 4 | 4 |
gnome |
4 | 4 | 6 | 8 | 12 |
windows_95 |
0 | 0 | 0 | 0 | 0 |
macos_sonoma |
6 | 6 | 10 | 14 | 20 |
ios_cupertino |
10 | 10 | 14 | 20 | 28 |
brutalist |
0 | 0 | 0 | 0 | 0 |
glassmorphism |
8 | 12 | 16 | 24 | 32 |
terminal_classic |
0 | 0 | 0 | 0 | 0 |
carbon_ibm |
0 | 0 | 0 | 0 | 0 |
shadcn |
4 | 6 | 8 | 10 | 12 |
Full table in themes/ui-style.kmd preset declarations.
R4 — Asymmetric shape
Sheets attached to screen edges use asymmetric radius — rounded only on edges that meet the screen.
.bottom-sheet {
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.right-pane {
border-radius: var(--radius-lg) 0 0 var(--radius-lg);
}R5 — Shape consistency rules
Within one surface:
- Mix at most 2 radii (excluding
radius-fullfor pills) - Smaller children inside larger containers (button
radius-sminsidecard
radius-md) - Never nest
radius-fullinsideradius-none(visual jarring)
Across the whole app:
- The preset defines THE shape language; deviation requires PR justification
R6 — Shape + motion
Shape can animate on state transitions per motion.kmd:
- Expanding FAB: pill → rounded rectangle (radius shrinks)
- Collapsing card: stays at constant radius (don't animate shape for state changes)
- Modal entry: scales up, radius constant
@media (prefers-reduced-motion: reduce) disables shape transitions.
R7 — Forbidden patterns
- ❌ Per
corner control on standard widgets (`borderradius: 8px 12px 4px 16px`) — only asymmetric for sheet edges (R4) - ❌ Negative shape (clip-path that cuts corners INWARD) — confusing
- ❌ Non-token radius values in widget code (use role binding)
- ❌ Animating radius repeatedly (looping shape morph) — distracting
Cross-link
ui-style.kmd— preset-specific radius scalesfoundations/elements.kmd— element family ↔ radius bindingthemes/motion.kmd— shape transitions during state changes