Skip to content

Shape

themes specs/themes/shape.kmd

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`.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Shape

Facet Visual do Koder Design. Material parity: https://m3.material.io/styles/shape/overview-principles.

The shape scale (5 levels)

RoleDefault radius (px)Use
radius-none0Sharp corners — Bauhaus, brutalist, Windows 95, Carbon
radius-xs4Chip, badge, small button, tag
radius-sm8Default button, input field, tab, segmented control
radius-md12Card, dialog, sheet, container
radius-lg16Modal sheet, full-screen dialog, hero container
radius-xl24Floating action button, large sheet edge, ornament
radius-full999Pill 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:

FamilyDefaultNotes
Surface (page bg)nonePages don't have corners
Container (card)radius-mdStandard card frame
Container (sheet)radius-lg on edges that meet screen, radius-none elsewhere
Control (button)radius-smMost common control radius
Control (FAB)radius-xl or radius-fullPill/round
Control (chip)radius-xs or radius-fullPer preset
Control (input)radius-smMatch buttons
Decoration (badge)radius-fullAlways pill
Content (image)radius-mdOptional; depends on context
Modal/dialogradius-lgDistinct from regular cards

R3 — Per-preset variation (from ui-style.kmd)

The 35 ratified presets each define their own scale. Sample:

Presetxssmmdlgxl
verge (default)246816
material348121624
material244444
gnome446812
windows_9500000
macos_sonoma66101420
ios_cupertino1010142028
brutalist00000
glassmorphism812162432
terminal_classic00000
carbon_ibm00000
shadcn4681012

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-full for pills)
  • Smaller children inside larger containers (button radius-sm inside card radius-md)
  • Never nest radius-full inside radius-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 (border-radius: 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
  • ui-style.kmd — preset-specific radius scales
  • foundations/elements.kmd — element family ↔ radius binding
  • themes/motion.kmd — shape transitions during state changes

References