Material Design 3 Expressive Loading Indicator for React, Svelte 5, Vue 3, and vanilla JS.
Faithfully ported from Android's material-components-android — uses the exact SVG path geometry from Material Design shape assets, spring physics animation (k=200, ζ=0.6), and the dual rotation system from LoadingIndicatorAnimatorDelegate.java.
- 7 morphing shapes: Soft Burst → Cookie 9 → Pentagon → Pill → Sunny → Cookie 4 → Oval
- Spring-driven animation matching Android's
SpringForce - Pixel-perfect shapes from official Material Design SVG assets
- Canvas-based rendering with HiDPI support
- Tree-shakeable — only import the framework you use
- TypeScript-first with full type definitions
npm install @alerix/m3-loading-indicatorRun the vanilla JS example locally:
pnpm install
pnpm build
pnpm example:vanillaimport { M3LoadingIndicator } from "@alerix/m3-loading-indicator/react";
function App() {
return (
<>
{/* Default — 48px, uses currentColor */}
<M3LoadingIndicator />
{/* Custom size and color */}
<M3LoadingIndicator size={64} color="#6750A4" />
{/* Slow motion */}
<M3LoadingIndicator speed={0.5} />
{/* Paused */}
<M3LoadingIndicator paused />
</>
);
}<script lang="ts">
import M3LoadingIndicator from "@alerix/m3-loading-indicator/svelte";
</script>
<!-- Default -->
<M3LoadingIndicator />
<!-- Custom size and color -->
<M3LoadingIndicator size={64} color="#6750A4" />
<!-- Slow motion -->
<M3LoadingIndicator speed={0.5} />
<!-- Paused -->
<M3LoadingIndicator paused /><script setup>
import { M3LoadingIndicator } from "@alerix/m3-loading-indicator/vue";
</script>
<template>
<!-- Default -->
<M3LoadingIndicator />
<!-- Custom size and color -->
<M3LoadingIndicator :size="64" color="#6750A4" />
<!-- Slow motion -->
<M3LoadingIndicator :speed="0.5" />
<!-- Paused -->
<M3LoadingIndicator paused />
</template>Use the core API directly for custom renderers:
import {
M3Animator,
getMorphedShape,
drawIndicator,
setupCanvas,
} from "@alerix/m3-loading-indicator";
const canvas = document.querySelector("canvas")!;
const ctx = setupCanvas(canvas, 48);
const animator = new M3Animator();
function loop(ts: number) {
animator.update(ts);
const shape = getMorphedShape(animator.morph);
drawIndicator(ctx, 48, shape, animator.rotation, {
color: "#6750A4",
});
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);| Prop | Type | Default | Description |
|---|---|---|---|
size |
number |
48 |
CSS pixel size of the indicator |
color |
string |
"currentColor" |
Fill color |
sizeRatio |
number |
0.79 |
Shape-to-container ratio (38dp in 48dp) |
speed |
number |
1 |
Animation speed multiplier |
paused |
boolean |
false |
Pause the animation |
contained |
boolean |
false |
Render with circular container background |
containerColor |
string |
"rgba(0,0,0,0.08)" |
Container background color |
The contained variant draws a circular background behind the indicator, matching Android's ContainedLoadingIndicator:
// React
<M3LoadingIndicator contained />
<M3LoadingIndicator contained containerColor="#E8DEF8" color="#6750A4" /><!-- Svelte -->
<M3LoadingIndicator contained />
<M3LoadingIndicator contained containerColor="#E8DEF8" color="#6750A4" /><!-- Vue -->
<M3LoadingIndicator contained />
<M3LoadingIndicator contained container-color="#E8DEF8" color="#6750A4" />m3-loading-indicator
├── core/ # Framework-agnostic engine
│ ├── spring # Spring physics (matches Android SpringForce)
│ ├── shapes # SVG path parsing + 7 Material shapes
│ ├── animator # Animation controller (from AnimatorDelegate.java)
│ └── renderer # Canvas drawing + HiDPI setup
├── react # React component (useEffect + rAF)
├── svelte # Svelte 5 component ($effect + rAF)
└── vue # Vue 3 component (defineComponent + rAF)
Ported from LoadingIndicatorAnimatorDelegate.java:
- Linear timer: 650ms per shape cycle, infinite repeat
- Spring morph:
SpringForce(stiffness=200, dampingRatio=0.6)drives shape transitions — creates the organic overshoot/settle feel - Rotation:
(50° + 90°) × base + 50° × timeFraction + 90° × springFraction— constant rotation + spring-driven extra rotation per cycle
The 7 shapes use exact SVG path data from Material Design asset files, parsed at init time into 180-point arrays for smooth morphing:
- Soft burst — 8-pointed soft star
- Cookie 9 — 9-sided scalloped shape
- Pentagon — heavily rounded 5-sided polygon
- Pill — rotated rounded diamond
- Sunny — 12-faceted rounded polygon
- Cookie 4 — 4-sided scalloped shape
- Oval — horizontal ellipse
Apache 2.0 — see LICENSE and NOTICE.
Ported from material-components-android (Apache 2.0, Copyright The Android Open Source Project) with SVG shape data from Material Design (Apache 2.0, Copyright Google LLC).