Shape library
themes specs/themes/shape-library.kmd
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).
When this spec applies
Primary triggers
- Add shape morphing to a component
- Bind a component to a named expressive shape
All triggers
- Use a named shape (Cookie, Burst, Flower, etc.) in any Koder UI
- Animate a shape morph (FAB → toolbar, square ↔ pill)
- Implement Loading indicator (#065), Hero carousel (#076), or any Expressive surface that requires non-rectangular geometry
Specification body
Spec — Shape library
Facet Visual do Koder Design. Material parity: https://m3.material.io/styles/shape. Source of polygon paths: androidx.graphics.shapes.
Companion to shape.kmd:
shape.kmddefines the radius scale (xs/sm/md/lg/xl/full) bound to corners of rectangular surfaces (cards, buttons, sheets).shape-library.kmddefines the 35 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 |
Tier:
baselineshapes (#1-5) MUST be present in every preset.expressiveshapes (#6-35) are opt-in per preset; presets that disable Expressive (e.g.,terminal_classic,brutalist) MAY declare them as no-ops mapping to Square or Pill.
Source 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 per-surface re-derivation.
R2 — Morphing contract
Two named shapes A → B can morph if they share the same number of morph 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 asmotion.kmdR6. - Decorative-only shapes (#14-35 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, motion-sensitivity hazard) — only state-driven 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)
Cross-link
shape.kmd— radius scale for rectangular surfacesmotion.kmdR9 — spring tokens driving morphsthemes/ui-style.kmd— per-preset shape configcomponents/loading-indicator.kmd— primary morph consumer (#065)components/carousels.kmd— Hero variant morphs (#076)components/buttons.kmd— FAB morph, Split button trailing morph (#066)
References
specs/themes/shape.kmdspecs/themes/motion.kmdspecs/components/loading-indicator.kmdspecs/themes/ui-style.kmd