Shape

mandatory

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

  • ❌ Percorner 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
  • ui-style.kmd — preset-specific radius scales
  • foundations/elements.kmd — element family ↔ radius binding
  • themes/motion.kmd — shape transitions during state changes

Source: ../home/koder/dev/koder/meta/docs/stack/specs/themes/shape.kmd