loading-state-zoo
v0.4.1
Published
26 smooth, transform-only loading patterns as framework-agnostic web components with a thin React wrapper. Works in React, plain web, iOS and Android WebViews. Recommended production release.
Downloads
705
Maintainers
Readme
loading-state-zoo
26 loading patterns, one tiny package. Dots, spinners, orbs, shapes, bars, music and skeletons that tell the user "please wait" — made silky smooth and ready to use in React, plain websites, and iOS / Android apps.
Recommended production version. v0.4.1 is the stable, production-tested line of
loading-state-zoo. Installnpm i loading-state-zoo@^0.4.1for the latest recommended release.
Think of it as a small zoo of loading animals. You pick an animal, put it in your app's cage, and it starts moving right away. You can even change its color, size and speed — no scissors or glue required.
- No heavy setup. Works everywhere a web view works. Zero required runtime dependencies.
- Private by design. Patterns live inside their own shadow DOM — their styles can never leak into your app, and your styles never break them.
- Smooth. Only
transformandopacityever animate, so it stays at 60fps even on phones. - Tunable. Every color, size, speed AND every keyframe number is customizable (see Colors & sizes and Tune the keyframes).
Looking for AI-friendly docs? See the Loading State Zoo documentation site → https://loading-state-zoo.pages.dev/docs
The animals in the zoo
| Name | Kind | Good for | | --- | --- | --- | | Bouncing dots | Dots | Loading a feed or a small section | | Typing dots | Dots | Chat apps, message "typing…" feel | | Wave dots | Dots | Waiting for a refresh or sync | | Helix dots | Dots | Data processing, fun & playful | | Dot grid wave | Dots | Brick/grid placeholders, dashboards | | Ellipsis | Dots | Chat, messaging, "hold on" moments | | Arc spinner | Spinner | Classic circular loading | | Material double arc | Spinner | Android/Material look and feel | | Scan sweep | Spinner | Radar/sonar, scanning, searching | | Orbit dots | Spinner | Floaty, lightweight loading | | Orbit ring dots | Spinner | Cosmetic ring, data-heavy load | | Sonar | Spinner | Radar pings, location detection | | Eyes | Shape | Fun state, "watching" waiting | | Morphing square | Shape | Modern brand moments | | Breathing pulse | Shape | Subtle, gentle "alive" loading | | Equalizer | Bars & rings | Music, audio, waveform | | Progress ring | Bars & rings | Showing exact percent done | | Progress bar | Bars & rings | Showing exact percent done, linear | | Indeterminate progress bar | Bars & rings | "Doing something, no % yet" bar | | Activity rings | Bars & rings | Fitness, health, Apple Watch style | | Waveform | Music | Audio apps, playing states | | Vinyl | Music | Music apps, retro feels | | Music note | Music | Music players, playful waits | | Skeleton shimmer | Skeleton | Placeholder while real content loads | | Skeleton card | Skeleton | Composite card/feed placeholders | | Skeleton list | Skeleton | Chat/inbox/search list placeholders |
Install
npm install loading-state-zooOne package, three styles of use:
| Entry point | What you get |
| --- | --- |
| loading-state-zoo | Web components (works everywhere) |
| loading-state-zoo/react | The React components |
| loading-state-zoo/theme.css | Plain CSS + markup (no JavaScript) |
Quick start: React
import { BouncingDots, ArcSpinner, SkeletonShimmer, ProgressRing } from "loading-state-zoo/react";
export function MyLoader() {
return (
<>
<BouncingDots />
<ArcSpinner size={64} color="#0a84ff" />
<SkeletonShimmer width={240} height={20} />
<ProgressRing percent={65} size={56} strokeWidth={6} />
</>
);
}That's it. Importing the package automatically registers all 26 elements for you (it's safe to import in many places — duplicates are skipped). It is also safe on the server (SSR/Next.js): on Node there is no browser, so nothing registers and nothing breaks.
If you prefer to register manually:
import { defineAll } from "loading-state-zoo"; // or from "loading-state-zoo/react"
defineAll();Quick start: plain website
Add the package from a CDN (or bundle it) and write the tags directly in your HTML.
<script type="module">
import "https://unpkg.com/loading-state-zoo";
</script>
<div style="display:flex; gap:24px; align-items:center">
<lz-wave-dots></lz-wave-dots>
<lz-orbit-dots size="56" color="#30d158"></lz-orbit-dots>
<lz-scan-sweep size="56" color="#0a84ff"></lz-scan-sweep>
<lz-morphing-square accent="#ff9f0a" speed="0.7"></lz-morphing-square>
</div>Mix and match any of the 26 tags — see the full list below.
Quick start: CSS only (no JavaScript at all)
Sometimes you just want markup and CSS — great for email-ish constraints, or for shipping the exact look to an iOS/Android WebView without any JS.
<link rel="stylesheet" href="https://unpkg.com/loading-state-zoo/theme.css" />
<div style="--lz-color:#0a84ff; --lz-speed:0.8">
<span class="lz-dot-grid-wave" style="--lz-size:8px; --lz-grid-gap:14px">
<i class="lz-dot"></i><i class="lz-dot"></i><i class="lz-dot"></i>
<i class="lz-dot"></i><i class="lz-dot"></i><i class="lz-dot"></i>
</span>
</div>Every pattern also ships as a .lz-<pattern> CSS class with .lz-<part> children. The part markup is identical to what the web components render — open any pattern on the demo site, inspect it, and copy the inner HTML.
Quick start: iOS & Android (WebView)
Any of the three styles above (web components or the CSS classes) run inside a WKWebView (iOS) or an Android WebView. The patterns are just HTML + CSS — no framework runtime, no bundler magic, nothing native to install.
- Bundle the ESM file into your app's assets:
- iOS: copy
dist/index.jsinto your Xcode target and load it inWKWebView. - Android: copy it into
assets/and inject it withWebView#evaluateJavascript, or load it as a local<script>.
- iOS: copy
- Or skip JS entirely and embed the HTML +
theme.cssdirectly (see above).
Colors and sizes
Yes — every component's color and size are customizable, three different ways. You can also change the accent color, stroke thickness, dot count and more.
1. By attribute (easiest)
<lz-bouncing-dots size="12" color="#30d158"></lz-bouncing-dots>
<lz-arc-spinner size="64" stroke-width="6" color="#ff9f0a"></lz-arc-spinner>
<lz-progress-ring percent="70" size="56" stroke-width="6" color="#0a84ff" track-color="rgba(255,255,255,0.2)"></lz-progress-ring>import { BouncingDots, ArcSpinner, ProgressRing } from "loading-state-zoo/react";
<>
<BouncingDots size={12} color="#30d158" />
<ArcSpinner size={64} strokeWidth={6} color="#ff9f0a" />
<ProgressRing percent={70} size={56} strokeWidth={6} color="#0a84ff" trackColor="rgba(255,255,255,0.2)" />
</>2. By CSS variable (theme whole groups)
Set a variable on a wrapper element and every pattern inside inherits it.
<div style="--lz-color:#0a84ff; --lz-accent:#30d158; --lz-speed:0.75">
<lz-wave-dots></lz-wave-dots>
<lz-orbit-dots></lz-orbit-dots>
<lz-morphing-square></lz-morphing-square>
</div>3. Common attributes (available on every pattern)
| Attribute | React prop | What it does | Default |
| --- | --- | --- | --- |
| size | size | Main dimension in px (dot diameter, or spinner/box size) | pattern-specific |
| color | color | Main foreground color (dots, arcs, bars, eyes) | #f5f5f7 |
| accent | accent | Accent/brand color (morph square, progress bar, ring fill) | #0a84ff |
| speed | speed | Animation speed — 0.5 half speed, 2 double speed | 1 |
| width | width | Width in px (bars & skeletons) | pattern-specific |
| height | height | Height in px (bars & skeletons) | pattern-specific |
Shared CSS variables (all patterns)
| Variable | What it does | Default |
| --- | --- | --- |
| --lz-color | Foreground color everywhere | #f5f5f7 |
| --lz-accent | Accent color | #0a84ff |
| --lz-speed | Duration multiplier | 1 |
| --lz-surface | Track / skeleton background | #1c1c1e |
| --lz-stroke | Ring thickness (px) | 4px |
Tune the keyframes
Every number inside every animation is also a CSS variable. Change how far dots bounce, how much a pulse grows, how fast eyes blink — on one pattern or on a whole app.
<div style="--lz-bounce:-16px; --lz-typing-peak:1.5; --lz-helix:-18px">
<lz-bouncing-dots></lz-bouncing-dots>
<lz-typing-dots></lz-typing-dots>
<lz-helix-dots></lz-helix-dots>
</div>| Variable | Pattern it changes | What it is | Default |
| --- | --- | --- | --- |
| --lz-bounce | Bouncing dots | How high the dots jump | -10px |
| --lz-typing-min | Typing dots | Smallest dot size | 0.35 |
| --lz-typing-peak | Typing dots | Biggest dot size | 1.15 |
| --lz-wave-min | Wave dots | Smallest dot size | 0.35 |
| --lz-wave-peak | Wave dots | Biggest dot size | 1.1 |
| --lz-helix | Helix dots | How far dots bob up | -12px |
| --lz-grid-min | Dot grid wave | Smallest dot size | 0.3 |
| --lz-grid-fade | Dot grid wave | Fade of resting dots | 0.15 |
| --lz-breath | Breathing pulse | How much it grows | 1.14 |
| --lz-morph | Morphing square | Smallest scale | 0.72 |
| --lz-morph-radius | Morphing square | Corner roundness at mid-morph | 17% |
| --lz-gaze-from | Eyes | Pupil start offset | -3px |
| --lz-gaze-to | Eyes | Pupil end offset | 3px |
| --lz-blink | Eyes | How closed the blink gets | 0.08 |
| --lz-sheen-from | Skeleton shimmer | Sheen start position | -120% |
| --lz-sheen-to | Skeleton shimmer | Sheen end position | 220% |
| --lz-orbit-fade | Orbit ring dots | Fade of resting dots | 0.12 |
| --lz-eq-min | Equalizer | Shortest bar height | 0.3 |
| --lz-slide-from | Indeterminate bar | Slider start position | -100% |
| --lz-slide-to | Indeterminate bar | Slider end position | 300% |
| --lz-wf-min | Waveform | Resting bar scale | 0.22 |
| --lz-sonar-fade | Sonar | Ring start opacity | 0.9 |
| --lz-note-bob | Music note | Note bob height | 0.35em |
Full component reference
Each section lists:
- What it is — plain words
- Good for — when to pick it
- HTML — the tag + attributes
- React — the component + props
- Extra attributes & variables
Bouncing dots
What it is: three round dots that hop up and down, one after another. Good for: waiting on a list, feed, or small section refresh.
<lz-bouncing-dots></lz-bouncing-dots>
<lz-bouncing-dots size="12" color="#0a84ff"></lz-bouncing-dots><BouncingDots />
<BouncingDots size={12} color="#0a84ff" />Attributes: size (dot size, default 8) · color · speed.
Variables: --lz-color · --lz-speed · --lz-bounce.
Typing dots
What it is: three dots that blink in a soft typing rhythm. Good for: chat, messaging, "typing…" moments.
<lz-typing-dots></lz-typing-dots><TypingDots />Attributes: size (default 8) · color · speed.
Variables: --lz-typing-min · --lz-typing-peak.
Wave dots
What it is: four dots that swell like a gentle wave. Good for: refresh, sync, pull-in-progress.
<lz-wave-dots></lz-wave-dots><WaveDots />Attributes: size (default 8) · color · speed.
Variables: --lz-wave-min · --lz-wave-peak.
Helix dots
What it is: a long row of dots that bob like a travelling wave. Good for: data crunching, fun playful waits.
<lz-helix-dots count="14"></lz-helix-dots><HelixDots count={14} />Attributes: size (default 6) · count (default 10) · color · speed.
Variables: --lz-helix.
Dot grid wave
What it is: a grid of dots that ripple outward from a corner. Good for: brick layouts, dashboards, big empty panels.
<lz-dot-grid-wave rows="4" cols="6" size="8" gap="14"></lz-dot-grid-wave><DotGridWave rows={4} cols={6} size={8} gap={14} />Attributes: size (default 6) · rows (default 4) · cols (default 5) · gap (default 12) · color · speed.
Variables: --lz-grid-gap · --lz-grid-min · --lz-grid-fade.
Ellipsis
What it is: three periods that appear in a typewriter rhythm. Good for: chat, messaging, text queues.
<lz-ellipsis size="10" color="#0a84ff"></lz-ellipsis><Ellipsis size={10} color="#0a84ff" />Attributes: size (default 8) · color · speed.
Variables: --lz-ellipsis-min · --lz-ellipsis-fade.
Arc spinner
What it is: one spinning "pac-man" ring. The classic. Good for: any circular wait.
<lz-arc-spinner size="64" stroke-width="6" color="#0a84ff"></lz-arc-spinner><ArcSpinner size={64} strokeWidth={6} color="#0a84ff" />Attributes: size (default 44) · stroke-width (default 4) · color · speed.
Variables: --lz-stroke.
Material double arc
What it is: two spinning arcs, opposite directions — the Material Design look. Good for: Android-style apps, "processing".
<lz-material-double-arc size="56" stroke-width="5"></lz-material-double-arc><MaterialDoubleArc size={56} strokeWidth={5} />Attributes: size (default 44) · stroke-width (default 4) · color · color-weak (second arc tint) · speed.
Variables: --lz-stroke · --lz-color-weak.
Scan sweep
What it is: concentric rings with crosshair ticks, a counter-rotating dial and a sweeping beam — like a radar. Good for: scanning, searching, syncing data.
<lz-scan-sweep size="64" color="#30d158"></lz-scan-sweep><ScanSweep size={64} color="#30d158" />Attributes: size (default 44) · color · color-weak · color-sweep · speed.
Variables: --lz-track · --lz-track-weak · --lz-color-weak · --lz-color-sweep.
Orbit dots
What it is: three dots glide around an invisible circle. Good for: light, floaty waits.
<lz-orbit-dots size="56" color="#0a84ff"></lz-orbit-dots><OrbitDots size={56} color="#0a84ff" />Attributes: size (default 44) · color · speed.
Variables: --lz-color-weak (dot tint).
Orbit ring dots
What it is: eight dots on a ring that fade in a chasing sequence. Good for: elegant circular waits, brand moments.
<lz-orbit-ring-dots size="56" dot-size="10"></lz-orbit-ring-dots><OrbitRingDots size={56} dotSize={10} />Attributes: size (default 44) · dot-size (default derived) · color · speed.
Variables: --lz-dot-size · --lz-orbit-fade.
Eyes
What it is: two cartoon eyes that look side to side and blink. Good for: fun apps, "watching" moments, kids' stuff.
<lz-eyes size="56"></lz-eyes><Eyes size={56} />Attributes: size (default 44) · color (sclera) · speed.
Variables: --lz-pupil (pupil color) · --lz-gaze-from · --lz-gaze-to · --lz-blink.
Morphing square
What it is: a glowing square that swells, rounds and spins. Good for: modern splash states, brand colors.
<lz-morphing-square accent="#bf5af2" size="56"></lz-morphing-square><MorphingSquare accent="#bf5af2" size={56} />Attributes: size (default 44) · accent (default #0a84ff) · speed.
Variables: --lz-glow (glow color/shadow) · --lz-morph · --lz-morph-radius.
Breathing pulse
What it is: one soft circle that gently grows and shrinks. Good for: subtle "alive" waits, background states.
<lz-breathing-pulse size="48" color="#30d158"></lz-breathing-pulse><BreathingPulse size={48} color="#30d158" />Attributes: size (default 36) · color · speed.
Variables: --lz-breath.
Equalizer
What it is: four bars dancing like a music equalizer. Good for: audio, music, waveform loading.
<lz-equalizer size="24" bar-width="4" gap="4" color="#ff375f"></lz-equalizer><Equalizer size={24} barWidth={4} gap={4} color="#ff375f" />Attributes: size (bar height, default 22) · bar-width (default 3) · gap (default 3) · color · speed.
Variables: --lz-bar-width · --lz-gap · --lz-eq-min.
Progress ring
What it is: a ring that fills to a percentage. Good for: uploads, downloads, tasks with a known percent. (Not animated by itself — you drive it with the percent attribute.)
<lz-progress-ring percent="65" size="56" stroke-width="6" color="#0a84ff" track-color="rgba(255,255,255,0.15)"></lz-progress-ring><ProgressRing percent={65} size={56} strokeWidth={6} color="#0a84ff" trackColor="rgba(255,255,255,0.15)" />Attributes: size (default 44) · stroke-width (default 4) · percent (default 0, clamped to 0–100) · color (fill) · track-color · speed.
Variables: --lz-stroke · --lz-track.
Accessibility: it exposes role="progressbar" plus aria-valuenow/min/max, and updates them when percent changes.
Progress bar
What it is: a linear bar that fills to a percentage. Good for: uploads, downloads, tasks with a known percent. (Not animated by itself — you drive it with the percent attribute.)
<lz-progress-bar width="220" height="8" percent="60" color="#0a84ff" track-color="rgba(255,255,255,0.15)"></lz-progress-bar><ProgressBar width={220} height={8} percent={60} color="#0a84ff" trackColor="rgba(255,255,255,0.15)" />Attributes: width (default 160) · height (default 6) · percent (default 0) · color (fill) · track-color · speed.
Variables: --lz-track.
Accessibility: it exposes role="progressbar" plus aria-valuenow/min/max, and updates them when percent changes.
Indeterminate progress bar
What it is: a bar with a slider that slides back and forth. Classic "we're working on it". Good for: long unknown tasks.
<lz-indeterminate-progress-bar width="220" height="8" accent="#0a84ff"></lz-indeterminate-progress-bar><IndeterminateProgressBar width={220} height={8} accent="#0a84ff" />Attributes: width (default 160) · height (default 6) · accent (default #0a84ff) · track-color · speed.
Variables: --lz-accent · --lz-surface · --lz-slide-from · --lz-slide-to.
Activity rings
What it is: three Apple Watch-style activity rings spinning at different speeds and directions. Good for: fitness apps, health dashboards, workout sync.
<lz-activity-rings size="64"></lz-activity-rings><ActivityRings size={64} />Attributes: size (default 44) · accent (outer, default #fa114f) · color (middle, default #30d158) · color-weak (inner, default #32d7ff) · speed.
Variables: --lz-accent · --lz-color · --lz-color-weak.
Waveform
What it is: a mirrored audio waveform pulsing around a center line. Good for: audio apps, playing states, podcast waits.
<lz-waveform count="15" accent="#bf5af2"></lz-waveform><Waveform count={15} accent="#bf5af2" />Attributes: size (height, default 32) · count (bars, default 11) · color · accent · bar-width (default 4) · gap (default 4) · speed.
Variables: --lz-bar-width · --lz-gap · --lz-wf-min.
Vinyl
What it is: a spinning record with a light sheen, label, spindle and a gently bobbing tonearm. Good for: music apps, retro waits, album screens.
<lz-vinyl accent="#fa114f"></lz-vinyl><Vinyl accent="#fa114f" />Attributes: size (default 48) · accent (label/stylus, default #0a84ff) · color (tonearm) · speed.
Sonar
What it is: a pulsing core emitting expanding sonar rings. Good for: radar pings, location detection, motion waits.
<lz-sonar count="4" accent="#30d158"></lz-sonar><Sonar count={4} accent="#30d158" />Attributes: size (default 44) · count (rings, default 3) · accent (ring color) · color (core) · speed.
Variables: --lz-sonar-fade.
Music note
What it is: drawn musical notes with heads, stems and flags bobbing in a sing-song rhythm. Good for: music players, karaoke, playful waits.
<lz-music-note count="3" color="#bf5af2"></lz-music-note><MusicNote count={3} color="#bf5af2" />Attributes: size (default 32) · count (notes, default 2) · color · speed.
Variables: --lz-note-bob.
Skeleton shimmer
What it is: a rounded block with a light that sweeps across. Good for: placeholders while real content loads (images, profiles, cards).
<lz-skeleton-shimmer width="240" height="24"></lz-skeleton-shimmer><SkeletonShimmer width={240} height={24} />Attributes: width (default 120) · height (default 28) · track-color · speed.
Variables: --lz-surface · --lz-sheen (sweep light color) · --lz-radius · --lz-sheen-from · --lz-sheen-to.
Skeleton card
What it is: a composite card skeleton with an avatar circle and text lines under one shared sheen. Good for: profile cards, feed items, user rows.
<lz-skeleton-card width="240" height="72"></lz-skeleton-card><SkeletonCard width={240} height={72} />Attributes: width (default 200) · height (default 64) · track-color · speed.
Variables: --lz-surface · --lz-radius · --lz-sheen-from · --lz-sheen-to.
Skeleton list
What it is: repeated list rows with avatars and text lines under one shared sheen. Good for: chat lists, inbox rows, search results.
<lz-skeleton-list width="220" rows="5"></lz-skeleton-list><SkeletonList width={220} rows={5} />Attributes: width (default 180) · rows (default 4) · track-color · speed.
Variables: --lz-surface · --lz-radius · --lz-sheen-from · --lz-sheen-to.
Accessibility
- Every pattern is announced to screen readers: dots/spinners/bars use
role="status"witharia-label="Loading". - The progress ring uses
role="progressbar"with livearia-valuenow,aria-valueminandaria-valuemax. - Reduced motion: the package does not force-disable animations, so you keep full control:
@media (prefers-reduced-motion: reduce) {
lz-bouncing-dots, lz-wave-dots,
lz-arc-spinner, lz-orbit-dots /* …and so on… */ {
animation-duration: 4s; /* or hide: animation: none */
}
}Performance & best practices
- Only
transformandopacityanimate — the compositor handles them, so the main thread stays free. - Fewer moving parts = cheaper. Prefer dots (Bouncing, Wave) for areas that appear often; keep spinners (Arc, Material) for a few spots.
- Swap
percenton the progress ring directly from your state — no animation frame loops needed. - The web components have zero global CSS — they can be dropped into the messiest existing page without side effects.
FAQ
Q: Do I need Tailwind or a bundler? No. The package is standalone.
Q: Does it collect any data? No. Nothing tracks, stores, or sends user data. It's just drawing.
Q: Do I need a privacy policy for this package? No. It's a code library, not a service, and it collects nothing.
Q: Can I use it in my commercial app? Yes — it's free for personal and commercial projects.
Q: Can I change sizes and colors? Yes — attributes, React props, or CSS variables. See Colors and sizes.
Q: iOS/Android native, not WebView? Not natively — but every modern nav/load UI is fine in a WebView; use the CSS-only approach for the smallest footprint.
For AI assistants (LLMs, coding agents)
Full human- and AI-friendly documentation lives at https://loading-state-zoo.pages.dev/docs. The packaged release also ships docs/AI.md (a condensed, machine-honest summary) and docs/components.json (the full structured spec for every component) if you want them offline.
Key facts an AI should always know:
- Auto-registration: importing any entry point registers all custom elements exactly once. Do not call
defineAll()again. - Entry points:
loading-state-zoo(web components),loading-state-zoo/react(React),loading-state-zoo/theme.css(plain CSS). - React prop ≈ attribute, but multi-word attributes are camelCased in React (
stroke-width→strokeWidth,track-color→trackColor). - Theming = CSS variables.
--lz-color,--lz-accent,--lz-speedon a parent inherit into all patterns inside it. - Keyframe numbers are variables too. There is no need to fork CSS to change motion — set
--lz-bounce,--lz-typing-peak, etc.
License
Free to use in personal and commercial projects. No fee, no attribution required (but appreciated).
