Pular para o conteúdo

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

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

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.kmd defines the radius scale (xs/sm/md/lg/xl/full) bound to corners of rectangular surfaces (cards, buttons, sheets).
  • shape-library.kmd defines 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)

#NameVerticesTierCanonical use
1Pill— (capsule)baselineButtons, chips, FAB
2Square4baselineCards, surfaces
3Rounded-square4 (squircle)baselineIcon containers, FAB at rest
4CirclebaselineAvatars, FABs, loading dots
5OvalbaselineHero carousel items
6Triangle3expressiveDecorative, alert glyphs
7Pentagon5expressiveLoading indicator phase
8Hexagon6expressiveLoading indicator phase, badge backing
9Slanted4 (parallelogram)expressiveStylized cards
10Arch— (semi + flat)expressiveHero sections
11SemicircleexpressiveHero footer cap
12HeartexpressiveLike/favorite action
13Diamond4 (rotated square)expressiveLoading phase
14Clover-44-lobeexpressiveLoading phase, decoration
15Clover-66-lobeexpressiveDecoration
16Clover-88-lobeexpressiveDecoration
17Burst12-spikeexpressiveLoading peak, "ta-da" reveal
18Flower8-petalexpressiveLoading mid, decoration
19Sunny8-rayexpressiveDecoration
20Very-sunny16-rayexpressiveDecoration, hero accent
21Cookie-44-notchexpressiveLoading start state
22Cookie-66-notchexpressiveLoading phase
23Cookie-77-notchexpressiveLoading phase
24Cookie-99-notchexpressiveLoading phase
25Cookie-1212-notchexpressiveLoading peak
26Boomirregular starexpressiveDecoration, badge
27Bun2-lobeexpressiveToggle backing
28Cubicrounded square 3D-feelexpressiveHero element
29Fanquarter-arcexpressiveDecoration
30Gem6 (rotated hex)expressiveBadge, premium-tier indicator
31Loaferrounded-rectangle stretchedexpressivePill-variant for wider buttons
32Pixel4 (sharp corners)expressiveCode/dev surfaces, terminal
33Puffy8-bumpexpressiveDecoration
34Pillartall rounded-rectexpressiveVertical hero element
35Star-1212-pointexpressiveAchievement, milestone

Tier:

  • baseline shapes (#1-5) MUST be present in every preset.
  • expressive shapes (#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

SourceDestinationUse
PillSquircleButton group hover/press
Rounded-squareCookie-12FAB pressed → menu open
Cookie-4BurstLoading indicator (low→high progress)
SquarePillCard → snackbar transition
CircleHeartLike button toggle
SquareOvalCarousel 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 contextToken
Hover/pressmotion-spatial-fast
State change (FAB morph)motion-spatial-default
Hero/decorativemotion-spatial-slow

R3 — Surface bindings

SurfaceAPI
Compose (Android, Wear)androidx.graphics.shapes.RoundedPolygon + Morph
FlutterKoderMorphableShape(from: KoderShape.cookie4, to: KoderShape.burst, t: progress) (wraps androidx.graphics.shapes via FFI for Android, custom polygon math elsewhere)
SwiftUICustom Shape conforming protocol + AnimatableData for vertex array interpolation
WebSVG <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.
  • 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

PresetShape behavior
material3 / material_expressiveAll 35 shapes; morphing enabled
material2Baseline only (#1-5); expressive shapes map to Square
terminal_classicAll shapes → Square (no rounded, no curves)
brutalistAll shapes → Pixel (#32) — sharp corners only
cyberpunk_neonAll 35 enabled; morph timing -20% (snappier)
glassmorphismAll 35 enabled; outline +1px blur during morph
minimalist_monoBaseline + 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)

Referências