Skip to content

Loading indicator

components specs/components/loading-indicator.kmd

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.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Loading indicator

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

Princípios

  1. Short by contract — < 5s. Above that → Progress indicator (cross-link).
  2. Shape morph signature — cycles through shape-library.kmd shapes; NOT a spinning circle.
  3. Spring-driven — uses motion.kmd R9 spring tokens; NOT duration-based.
  4. Composable — primary consumer pra pull-to-refresh + button loading state.

R1 — When to use

Use caseUse Loading indicator?
Click "Save", wait < 5sYES
Pull-to-refresh feedYES
Long upload (> 5s, progress known)NO — use Progress (determinate)
Indeterminate > 5s, no progressNO — 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:

ContextCycle
Default (pull-to-refresh, button)Cookie-4 → Burst → Flower → Cookie-4
Compact (in chip, inline)Cookie-4 → Cookie-7 only (faster)
Hero (full-screen)Full library traversal (longer perceived)

R3 — Sizes + colors

Size tokenDiameter (dp)Stroke widthUse
sm242Inline (button, chip)
md403Pull-to-refresh, dialog
lg644Hero/empty state

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

StateColor override
Defaultprimary
Disabled contexttext-muted
Error retry contexterror

R4 — Pull-to-refresh integration

When hosted in pull-to-refresh (cross-link future pull-to-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 cross-fade (motion-effect-fast).

R6 — Surface bindings

SurfaceAPI
FlutterKoderLoadingIndicator({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 AndroidKoderLoadingIndicator via koder-design-compose (futuro)
SwiftUI iOSidem via koder-design-swift (futuro)
CLI / TUIn/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).
  • Pull-to-refresh: skip drag-distance-driven 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

Keyen-USpt-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

PresetLoading indicator behavior
material3 / material_expressiveDefault (shape morph)
material2Circular spinner (no morph) — fallback to old Material progress
terminal_classicASCII spinner `
brutalistSquare block 100% color toggle (no curves)
cyberpunk_neonDefault + glow halo
minimalist_monoSingle thin line scaling 0% → 100% width
glassmorphismDefault + backdrop blur ring

T-suite

  • T1 Mount: render with size md → Cookie-4 shape visible.
  • T2 Morph cycle: advance time 1.2s → cycles through Cookie-4 → Burst → Flower → Cookie-4.
  • T3 Sizes: render sm/md/lg → diameter 24/40/64dp; stroke 2/3/4dp.
  • T4 Color override: render with error role → red.
  • T5 Pull-to-refresh integration: drag-threshold cross → morph begins; release → snap-back smooth.
  • T6 Button loading: button enters loading → content replaced; bounds preserved.
  • T7 Reduced-motion: animations disabled → opacity pulse only; aria-live announces.
  • T8 A11y: aria-live "polite"; aria-label correct in active locale.
  • N1 Long action (>5s): policy violation lint should warn — Loading indicator NOT for > 5s use cases.

References