Loading indicator

mandatory

Material 3 Expressive Loading indicator — distinct from Progress indicators. Morphing shape (Cookie → Burst → Flower → Cookie cycle) via spring physics. Used for actions < 5s; replaces most indeterminate circular spinners. Integrates with pull-to-refresh.

Spec — Loading indicator

Companion: progress-indicators.kmd cobre determinate + indeterminate progresso de longo prazo. Esta spec cobre *oading short < 5s*com shape morphing — pattern Expressive.

Princípios

  1. *hort by contract*— < 5s. Above that → Progress indicator (cross-link).
  2. *hape morph signature*— cycles through shape-library.kmd shapes; NOT a spinning circle.
  3. *pringdriven*— uses motion.kmd R9 spring tokens; NOT durationbased.
  4. *omposable*— primary consumer pra pulltorefresh + button loading state.

R1 — When to use

Use case Use Loading indicator?
Click "Save", wait < 5s YES
Pulltorefresh feed YES
Long upload (> 5s, progress known) NO — use Progress (determinate)
Indeterminate > 5s, no progress NO — use Progress (indeterminate)
Background sync (invisible to user) NO — toast/snackbar only
Pre-action loading (button) YES — replaces button content briefly

Decision tree formalized:

Action duration estimable?
├── YES: use Progress (determinate, %)
└── NO:
    ├── < 5s expected → Loading indicator (this spec)
    └── ≥ 5s expected → Progress (indeterminate)

R2 — Shape morph cycle

Default morph sequence (per shape-library.kmd):

Cookie-4 → Cookie-7 → Burst → Flower → Cookie-4 → ...

Each transition driven by motion-spatial-default spring (per motion.kmd R9.1). Cycle duration: ~1.2s for full loop (3 morphs × 400ms each).

Configurable per consumer:

Context Cycle
Default (pulltorefresh, button) Cookie4 → Burst → Flower → Cookie4
Compact (in chip, inline) Cookie4 → Cookie7 only (faster)
Hero (full-screen) Full library traversal (longer perceived)

R3 — Sizes + colors

Size token Diameter (dp) Stroke width Use
sm 24 2 Inline (button, chip)
md 40 3 Pulltorefresh, dialog
lg 64 4 Hero/empty state

Color: primary color role per themes/color-roles.kmd. Per-state override:

State Color override
Default primary
Disabled context text-muted
Error retry context error

R4 — Pulltorefresh integration

When hosted in pulltorefresh (crosslink future `pullto-refresh.kmd` #075):

  • Drag distance < threshold (40dp): static Cookie-4 (smaller scale 0.6).
  • Drag distance ≥ threshold: morph begins (continuous).
  • Release < threshold: snap-back spring; indicator disappears.
  • Release ≥ threshold: refresh triggers; indicator continues cycling until completion.

R5 — Button loading state

When button enters loading (per buttons.kmd):

  • Replace button content (text + icon) with Loading indicator (size sm).
  • Maintain button bounds (no width jump).
  • Button disabled (no double-tap).
  • On completion: replace back to original content via crossfade (`motioneffect-fast`).

R6 — Surface bindings

Surface API
Flutter KoderLoadingIndicator({size, color}) em koder_kit/lib/src/ai/ (futuro) OR koder_kit/lib/src/loading/
Web <koder-loading-indicator size="md"> em koder_web_kit
Compose Android KoderLoadingIndicator via koder-design-compose (futuro)
SwiftUI iOS idem via koder-design-swift (futuro)
CLI / TUI n/a (terminal usa spinner ASCII canônico)

R7 — Reduced-motion

prefers-reduced-motion: reduce:

  • Morph cycle disabled.
  • Static shape (default Cookie-4) with opacity pulse 0.5↔1.0 over 1.2s (visual "still active" signal).
  • Pulltorefresh: skip dragdistancedriven morph; show static indicator on threshold cross.

R8 — Acessibilidade

  • Container: role="status" aria-live="polite" aria-label="Loading" (i18n).
  • After done: live region announces "Done" (configurable per context).
  • Cursor: pointer if interactive (rare; cancel button siblings).
  • Visual ≥ 24dp minimum (sm).

R9 — i18n

Key en-US pt-BR
loading.label.default "Loading" "Carregando"
loading.label.refreshing "Refreshing" "Atualizando"
loading.label.saving "Saving" "Salvando"
loading.label.done "Done" "Concluído"

R10 — Per-preset variation

Preset Loading indicator behavior
material3 / material_expressive Default (shape morph)
material2 Circular spinner (no morph) — fallback to old Material progress
terminal_classic ASCII spinner ` /-` cycle
brutalist Square block 100% color toggle (no curves)
cyberpunk_neon Default + glow halo
minimalist_mono Single thin line scaling 0% → 100% width
glassmorphism Default + backdrop blur ring

T-suite

  • *1*Mount: render with size md → Cookie-4 shape visible.
  • *2*Morph cycle: advance time 1.2s → cycles through Cookie4 → Burst → Flower → Cookie4.
  • *3*Sizes: render smmdlg → diameter 244064dp; stroke 234dp.
  • *4*Color override: render with error role → red.
  • *5*Pulltorefresh integration: dragthreshold cross → morph begins; release → snapback smooth.
  • *6*Button loading: button enters loading → content replaced; bounds preserved.
  • *7*Reducedmotion: animations disabled → opacity pulse only; arialive announces.
  • *8*A11y: arialive "polite"; arialabel correct in active locale.
  • *1*Long action (>5s): policy violation lint should warn — Loading indicator NOT for > 5s use cases.

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/loading-indicator.kmd