Shape library

mandatory

Catalog of 35 named shapes + morphing contract for Material 3 Expressive parity. Sibling of `shape.kmd` (radius scale) — this spec covers polygon shapes (Pill, Cookie, Burst, Flower, etc.) and the interpolation contract that animates between two shapes via spring physics (`motion.kmd` R9). Source-of-truth for shape paths: androidx.graphics.shapes (vendored per-surface).

Spec — Shape library

Facet *isual*do Koder Design. Material parity: https://m3.material.io/styles/shape. Source of polygon paths: androidx.graphics.shapes.

Companion to shape.kmd:

  • shape.kmd defines the *adius scale*(xssmmdlgxl/full) bound

    to corners of rectangular surfaces (cards, buttons, sheets).

  • shape-library.kmd defines the *5 named polygon shapes*used

    for non-rectangular surfaces (loading indicators, FAB morphs, Hero carousels, expressive decorations).

Both coexist: a Card uses shape.kmd radius tokens; a Loading indicator (#065) uses shape-library.kmd polygon shapes.

R1 — Catalog (35 shapes)

# Name Vertices Tier Canonical use
1 Pill — (capsule) baseline Buttons, chips, FAB
2 Square 4 baseline Cards, surfaces
3 Rounded-square 4 (squircle) baseline Icon containers, FAB at rest
4 Circle baseline Avatars, FABs, loading dots
5 Oval baseline Hero carousel items
6 Triangle 3 expressive Decorative, alert glyphs
7 Pentagon 5 expressive Loading indicator phase
8 Hexagon 6 expressive Loading indicator phase, badge backing
9 Slanted 4 (parallelogram) expressive Stylized cards
10 Arch — (semi + flat) expressive Hero sections
11 Semicircle expressive Hero footer cap
12 Heart expressive Like/favorite action
13 Diamond 4 (rotated square) expressive Loading phase
14 Clover-4 4-lobe expressive Loading phase, decoration
15 Clover-6 6-lobe expressive Decoration
16 Clover-8 8-lobe expressive Decoration
17 Burst 12-spike expressive Loading peak, "ta-da" reveal
18 Flower 8-petal expressive Loading mid, decoration
19 Sunny 8-ray expressive Decoration
20 Very-sunny 16-ray expressive Decoration, hero accent
21 Cookie-4 4-notch expressive Loading start state
22 Cookie-6 6-notch expressive Loading phase
23 Cookie-7 7-notch expressive Loading phase
24 Cookie-9 9-notch expressive Loading phase
25 Cookie-12 12-notch expressive Loading peak
26 Boom irregular star expressive Decoration, badge
27 Bun 2-lobe expressive Toggle backing
28 Cubic rounded square 3D-feel expressive Hero element
29 Fan quarter-arc expressive Decoration
30 Gem 6 (rotated hex) expressive Badge, premium-tier indicator
31 Loafer rounded-rectangle stretched expressive Pill-variant for wider buttons
32 Pixel 4 (sharp corners) expressive Code/dev surfaces, terminal
33 Puffy 8-bump expressive Decoration
34 Pillar tall rounded-rect expressive Vertical hero element
35 Star-12 12-point expressive Achievement, milestone

*ier:*

  • baseline shapes (#1-5) MUST be present in every preset.
  • expressive shapes (#635) are optin per preset; presets that

    disable Expressive (e.g., terminal_classic, brutalist) MAY declare them as no-ops mapping to Square or Pill.

*ource of truth:*polygon vertex coordinates and SVG paths come from androidx.graphics.shapes (vendored under engines/sdk/koder-design-compose/shapes/ once #062.G11 ships). Other surfaces (Flutter, SwiftUI, Web) re-export the same vertex data — no persurface rederivation.

R2 — Morphing contract

Two named shapes A → B can morph if they share the same number of *orph vertices*(vertex correspondence map). Where vertex counts differ, the library MUST subdivide the lower-count shape into the higher count before interpolation:

morph(Square, Cookie-12)
  → subdivide(Square) from 4 → 12 vertices (each edge into 3 segments)
  → interpolate(square_12v_i, cookie_12v_i) for i in 0..11
  → output frame

Subdivision is deterministic per androidx.graphics.shapes algorithm — same A,B always yields same intermediate frames.

R2.1 — Required morph pairs

Source Destination Use
Pill Squircle Button group hover/press
Rounded-square Cookie-12 FAB pressed → menu open
Cookie-4 Burst Loading indicator (low→high progress)
Square Pill Card → snackbar transition
Circle Heart Like button toggle
Square Oval Carousel item peek↔hero

Components that animate shape MUST declare their morph pair in their spec (e.g., loading-indicator.kmd R2 declares Cookie-4 → Burst → Flower → Cookie-4 cycle).

R2.2 — Animation driver

Shape morph MUST be driven by motion.kmd R9.1 spring tokens (spatial). Duration-driven morphs are forbidden because spring overshoot is part of the perceptual signature.

Morph context Token
Hover/press motion-spatial-fast
State change (FAB morph) motion-spatial-default
Hero/decorative motion-spatial-slow

R3 — Surface bindings

Surface API
Compose (Android, Wear) androidx.graphics.shapes.RoundedPolygon + Morph
Flutter KoderMorphableShape(from: KoderShape.cookie4, to: KoderShape.burst, t: progress) (wraps androidx.graphics.shapes via FFI for Android, custom polygon math elsewhere)
SwiftUI Custom Shape conforming protocol + AnimatableData for vertex array interpolation
Web SVG <path> with d attribute animated via Web Animations API; polygon paths precomputed at build time per preset

For Web, precompute morph frames at build time (e.g., 30 keyframes between A and B) and serve them as a CSS variable lookup table — do NOT compute polygon math at runtime in JS.

R4 — Accessibility

  • Shape morph honors prefers-reduced-motion: when set, MUST snap

    directly to end state (no intermediate frames). Same contract as motion.kmd R6.

  • Decorativeonly shapes (#1435 used purely for decoration) MUST

    carry aria-hidden="true" or platform equivalent.

  • Functional shapes (Loading indicator, toggle states) MUST announce

    state changes via the host component, not the shape itself.

R5 — Per-preset variation

Preset Shape behavior
material3 / material_expressive All 35 shapes; morphing enabled
material2 Baseline only (#1-5); expressive shapes map to Square
terminal_classic All shapes → Square (no rounded, no curves)
brutalist All shapes → Pixel (#32) — sharp corners only
cyberpunk_neon All 35 enabled; morph timing -20% (snappier)
glassmorphism All 35 enabled; outline +1px blur during morph
minimalist_mono Baseline + Pill + Squircle only; expressive collapse to Squircle

Per-preset shape disables MUST be declared explicitly in ui-style.kmd preset definition. Default fallback: missing preset config = all 35 enabled.

R6 — Forbidden patterns

  • ❌ Inline polygon vertex math in component code (use the library

    binding)

  • ❌ Shape morph without spring driver (per R2.2)
  • ❌ Decorative shapes that animate continuously (battery drain,

    motionsensitivity hazard) — only statedriven morphs allowed

  • ❌ Shape morph between shapes whose semantic meaning differs

    (e.g., Heart → Triangle) — morphs MUST be within a semantic family (loading, like, button-state)

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