Aura Coach — ClayLegacy format
Clay is the design system of Aura Coach AI, a Flutter app for learning English (UI in English and Vietnamese). It features pastel accents on a warm cream canvas, cards with offset shadows, dark ink accents, and press-down interactions.
Color Palette
Typography
Every letter tells a story worth reading, and every typeface gives that story a new voice waiting to be heard.
Every letter tells a story worth reading, and every typeface gives that story a new voice waiting to be heard.
Every letter tells a story worth reading, and every typeface gives that story a new voice waiting to be heard.
Every letter tells a story worth reading, and every typeface gives that story a new voice waiting to be heard.
Every letter tells a story worth reading, and every typeface gives that story a new voice waiting to be heard.
Every letter tells a story worth reading, and every typeface gives that story a new voice waiting to be heard.
Every letter tells a story worth reading, and every typeface gives that story a new voice waiting to be heard.
Clay is the design system of Aura Coach AI, a Flutter app for learning English (UI in English and Vietnamese). It features pastel accents on a warm cream canvas, cards with offset shadows, dark ink accents, and press-down interactions.
Components
Buttons
Cards
Card Title
Sample body text for the card component.
Elevation & Depth
Do's & Don'ts
Do
Don't
Icons
Material IconsLibrary: Material Icons · Style: outline · Size: 20px
Theming Axes (Brightness · Skin · Topic)
Three questions, three orthogonal axes, three ThemeExtensions on every ThemeData:
• Brightness — how bright is the room. ClayPalette flips chrome; accents stay.
• Skin — what a widget is MADE of: bands, drops, radii, press depth, on-accent ink. ClaySkinSurfaces. Persisted as aura.set.claySkin; picker in Settings › App › Skin.
• Topic — WHERE we are: ground, ornament vocabulary, later icons. ClayTopicScape. Persisted as aura.set.clayTopic; three topics are offered (shape, school, space); unknown values clamp to shape.
Any skin can sit in any topic (pebble-in-space is valid): a topic never touches widget grammar, a skin never touches the ground.
Skins
• classic — flat fills (raised/well/tray null), white on-accent ink, decorative border everywhere, no washes, sharp drops, motifLevel 0. Pinned by 18 literal hex tests. Exempt from AA.
• intaglio (default) — the 70/30 Clay×Lumi hybrid (spec count 135 Clay / 46 Lumi / 11 WCAG slots): canvas unchanged, band gradients, ink #1E202F, borderControl at ≥ 3.0:1, sharp drops kept, motifLevel 1.
• pebble — the 30/70 inverse: warm deep canvas separating by colour (1.17:1), blurred warm drops, radii 28/20, press 0.96, borders erased with width preserved, bands 18%, motifLevel 2.
Skin API a widget may use raised / well / tray · tinted(accent) · banded(fill, darkScale) · inkOn / bandFill / accentInkOr · inkBorderOr · focusRing · borderControl · radiusLg / radiusMd · pressScale · motifLevel · washFor(accent). Recipes: card-on-canvas → raised; accent control → tinted; arbitrary fill (tint card, white pill, notification row) → banded.
Skin and topic transitions snap the whole object at t = 0.5 rather than lerp per field: a half-lerped null gradient flipped every == null gate and rendered cards see-through for one frame.
Topics
• shape — today's app; every ground field null, bit-identical to no-topic.
• school — paper world in light mode: page #F2F6FC, surface #FFFFFF, ink #23293A, inkMuted #4F5769 (6.7:1). In dark mode it is the same room with the lights down — schoolDark runs at Brightness.dark on #1B1F2A → #222735 → #2A3040 with ink #EDF1F8. Stationery ornaments; accentMix 0.18. Only Space forces one brightness in both modes.
• space — indigo sky in BOTH brightnesses (theme forced dark): ground #141C44 → #212857 → #2A2660, chrome bg #1E2454, surface #232A58, border #3A4180, ink #EEF2FF, muted #C3CBEC, faint #A6ADD6; after-midnight variant #0A0F2A → #141A45 → #241F5C; celestial ornaments; accentMix 0.16 in light, 0.14 in dark. The horizon moved from #3B3585 to #2A2660 because its luminance (.088) sat under the ornament ceiling (.095) and flattened every ornament.
• The mode accent is blended into the horizon stop only (0 at the sky top); chat grounds use half the mix.
A topic with a ground recolours the WHOLE app: the palette comes from scape.paletteFrom(skin palette) and the skin's surfaces are rebasedOn(topic surface) — band gradients bake hue as well as geometry, so handing them over verbatim paints warm-brown cards on an indigo sky. Region-scoped theming was tried and removed. Geometry stays the skin's; colour becomes the topic's (test-locked).
Motion
Durations (ms) fast 150 (state swaps) · medium 200 (chips, check circles) · normal 300 (page transitions, message entrance) · press 80 · inputFocus 120 (faster than fast on purpose) · debounce 600 (also the StaggeredEntrance default) · stagger 800 (auth/onboarding) · score 900 (score sweep + count-up) · celebration 2000 · iconLoop 2400 (idle icon life) · float 2800 (mode-card icon drift 0 → −6px). Orphans with zero consumers: slow 500 (a Future.delayed only), typingWave 1200, pulse 2200, springGentle.
Curves. One house curve, easeClay = Curves.easeInOut. Press-in and StaggeredEntrance use easeOut; MessageEntrance uses easeClay. elasticOut appears at exactly two reward moments (score circle scale 0.6→1.0 over the first 50%; mind-map node pop capped at 1.08×) — an observed convention, not a declared rule. easeOutBack and bounceOut are absent.
Spring. springTap mass 1 / stiffness 400 / damping 15 (ζ ≈ 0.375 derived; documented as ~28% overshoot) — the only spring in the app, on press release. The spec rejected Lumi's 56% overshoot as too bouncy for a study app used in long sessions.
Page transitions. fadeTransitionPage for go() replacements (4 routes); slideFadeTransitionPage for push() (32 routes): fade + slide from 6% of height, 300ms easeClay.
Entrances. StaggeredEntrance: child i starts at i × 0.12 (clamped ≤ 0.7), window 0.4, 16px rise. MessageEntrance: 20px rise, 300ms. CelebrationOverlay: 40 confetti particles in the four accents, 4–10px, alpha 0.8, fading over the last 30% of 2000ms; no tiers — all three call sites use defaults.
Reduce motion (MediaQuery.disableAnimations): page transitions return the bare child; StaggeredEntrance becomes a Column; confetti is removed; icons hold frame 0; press skips the scale and snaps (haptic still fires); Lottie stops and the orb video never initialises. No test covers any motion token.
Accessibility Contract
Ruler. Every lock computes WCAG 2.x contrast from Color.computeLuminance: (Lmax + 0.05) / (Lmin + 0.05). Thresholds: text 4.5:1 (14px w800 button labels are NOT large text); UI boundaries and focus 3.0:1. All theme tests gate CI. No golden tests; no meetsGuideline.
Locked for intaglio and pebble, both brightnesses
• onAccent ≥ 4.5:1 on teal, gold, coral, purple, success, error; purpleDeep asserted to FAIL so nobody "completes" the map.
• inkOn(fill) pinned to #1E202F and ≥ 4.5:1 on bandFill(fill) for 9 fills; never worse than the previous ink on any fill (tolerance 0.005).
• textMuted ≥ 4.5:1 on background, surface, surfaceAlt (classic exempt).
• focusRing ≥ 3.0:1 vs the well mid-stop for teal, purple, gold, coral.
• Space topic: ink and inkMuted ≥ 4.5:1, inkFaint ≥ 3.0:1 on every ground stop; four accents ≥ 4.5:1 on the space surface; horizon > 0.025 below the ornament ceiling.
• Motifs: lighter than canvas, no shadow, textMuted ≥ 4.5:1 on every tone.
• Source guards: surfaces.onAccent outside core/theme only in two allow-listed story files; onAccent.withValues( forbidden; bare border widths forbidden.
• Band grammar: raised top brighter than bottom; well and tray inverted.
• Classic fidelity: 18 palette hexes, null gradients, white ink, (3,3) drop.
Classic debts pinned so they can neither rot nor be silently patched — warmDark on purple 4.42:1 (±0.01); white on story gradients worst 1.63:1 (±0.02); white on purpleDeep CTAs 4.22:1. Founder decides.
Measured absences
• No keyboard focus ring on any pressable (0 focus vs 124 pressable sites); the spec's two-tone ring (#14162A 3px / #FFFDF7 2px, self-contrast 17.53:1) is unimplemented.
• Min tap target 44 is sized explicitly at only a handful of shared-widget sites; there is no touch-target token.
• textFaint is unguarded and still used at ~65 live sites at 2.32–2.63:1; the textDisabled rename has not landed.
• AppColors.onAccent (#FFFFFF) survives at 43 painter sites (40 as glyph veils) outside the guard regex.
• The accent-as-text tint ramp (154/154 solved pairs per the spec's own arithmetic) is unimplemented; the pattern fails at 1.27–3.49:1 wherever used.
• borderControl is consumed at 3 sites; ~30–40 of 180 clay.border sites are real controls still on the 1.29:1 decorative hex.
• The streak heatmap uses teal at alpha 0.35 / 0.6 / 0.8, unmeasured; the spec's SC 1.4.11 ramp #39978D → #2E7971 → #245E58 → #1A443F is not in code.
• Painters baking warmDark render 1.00:1 on the dark surface; no guard.
• Classic fails AA broadly (textMuted 4.42, border 1.25, white on accents 1.63–2.92) — a fidelity snapshot, not a target.
Use with MCP
Don't have the MCP? Install it here