Loading Animations

Every loading icon in the repo — the quantum sphere orbital, eight React Spinner variants and 25 zero-dependency SVG animations — rendered live, with size and color controls.

The repo ships three collections that cover both ends of a download UI:

  • Quantum sphere — an interactive 3D orbital loader for React and Svelte, from quantum-sphere-loading-icon. The showpiece: it randomizes itself and reacts to the pointer.
  • <Spinner> — a shadcn/ui-style React component with eight variants, from loading-animations. Draws with currentColor, so a text color class is all it takes to restyle.
  • SVG functions — 25 animated SVGs returned as strings from a single function call. Zero dependencies, framework-free, and tree-shakable: bundlers drop anything you don't import.

Every animation below is live. Drag the size slider, load a preset theme or build your own palette one color at a time, then click any label to copy the exact call that produces what you see.

Quantum sphere

A parabolic spherical orbital, inspired by the quantum superposition of atomic orbitals. Each ring spins in its own 3D plane and holds a superposed state until it is observed — hover a ring and it collapses onto a fresh hue, glow and rotation speed. Every few seconds the whole sphere re-rolls its color scheme from 24 presets (Neon, Galaxy, Cyberpunk, …).

Hover a ring to collapse it — each line picks its own hue, glow and speed on contact.

It is the one loader here that is a component rather than a string, so it is also the only one that responds to the pointer. Give the wrapper a height — the sphere centers itself in whatever box it is handed.

Full demo: every setting is on a slider at grab.js.org/loaders/quantum-sphere, which prints the matching config ready to copy.

Install

Already included if you have grab-url — it re-exports the sphere from grab-url/icons/quantum-sphere. Standalone, it is its own package:

npm install quantum-sphere-loading-icon

Usage

// Bundled with grab-url:
import QuantumOrbital from 'grab-url/icons/quantum-sphere';
// Or standalone:
import QuantumOrbital from 'quantum-sphere-loading-icon/react';

export function Loading() {
  return (
    <div style={{ height: 400 }}>
      <QuantumOrbital autoRandomize />
    </div>
  );
}
<script>
  import QuantumOrbital from 'quantum-sphere-loading-icon/svelte';
</script>

<div style="height: 400px">
  <QuantumOrbital autoRandomize={true} />
</div>
PropTypeDefaultDescription
configOrbitalSphereConfigpresetMin/max bounds for line count, sphere size, line width, glow, rotation speed, saturation and lightness
autoRandomizebooleantrueRe-rolls the color scheme and shifts hue every 5–12s
classNamestring""Extra classes on the wrapper
onSphereClick() => voidnullFired when the sphere is clicked

config is expressed as ranges, and the component draws each value from its range on every re-roll. Setting a min equal to its max pins that dimension instead — which is how the demo page turns a randomizer into a picker:

import { DEFAULT_ORBITAL_SPHERE_CONFIG } from 'grab-url/icons/quantum-sphere';

// A fixed 9-ring, 180px sphere that still re-rolls its colors.
<QuantumOrbital
  config={{
    ...DEFAULT_ORBITAL_SPHERE_CONFIG,
    minLines: 9,
    maxLines: 9,
    minSphereSize: 180,
    maxSphereSize: 180,
  }}
/>

React <Spinner>

Color
Loading...
Loading...
Loading...
Loading...

Install

The component is a single file with no runtime dependency beyond lucide-react. Copy components/ui/spinner.tsx into your own components/ui folder — the folder shadcn/ui writes to, so the component sits alongside button, badge and the rest and stays overwritable by the shadcn CLI.

npm install lucide-react

If your project has no shadcn/ui setup yet, scaffold one first — it creates components/ui, lib/utils.ts (the cn helper the component imports) and the Tailwind theme tokens:

npx shadcn@latest init

Usage

import { Spinner } from '@/components/ui/spinner';

export function SaveButton({ pending }: { pending: boolean }) {
  return (
    <button type="submit" disabled={pending}>
      {pending ? <Spinner variant="circle" size={16} /> : null}
      Save
    </button>
  );
}

SpinnerProps extends LucideProps, so size, className, strokeWidth and any SVG attribute pass straight through:

<Spinner />                                          // spokes (default)
<Spinner variant="circle" size={48} />
<Spinner variant="bars" className="text-fd-primary" />
<Spinner variant="ring" size={64} strokeWidth={1} />
VariantLook
default8 radiating spokes, rotating
circleOpen circular arc, rotating
pinwheelThree interlocking arcs inside a circle
circle-filledRotating arc over a dimmed full-circle track
ellipsisThree dots bouncing in sequence
ringTwo rings expanding and fading outward
barsThree bars pulsing like an equalizer
infiniteDash travelling around an infinity loop

SVG animations

Call a function, get an SVG string. Nothing here depends on React, so the same icons work in vanilla DOM, Svelte, Vue, or an email template.

Theme
Colors#71717a

Icons are drawing their built-in colors. Pick a theme or edit a swatch to apply this palette.

new
new
new
new
new
new
new
new

Usage

import { loadingSpokes, loadingPacman } from 'loading-animations/svg';

// Default: an <img> tag wrapping a data URI — safe to drop anywhere.
element.innerHTML = loadingSpokes({ size: 48 });

// raw: true returns the <svg> markup itself, so it inherits page styles.
element.innerHTML = loadingPacman({ size: 64, colors: ['#0099e5', '#ff4c4c'], raw: true });
type LoadingOptions = {
  /** Hex colors substituted into the SVG, in source order. */
  colors?: string[];
  /** Width in px. */
  width?: number;
  /** Height in px. */
  height?: number;
  /** Shorthand that sets both width and height. */
  size?: number;
  /** Return raw `<svg>` markup instead of an `<img>` data URI. */
  raw?: boolean;
};

Colors

colors is substituted into the SVG in source order: the first entry replaces the first color in the markup, the second the second, and so on. Anything the array doesn't reach keeps its original color, so the list only needs to be as long as the icon is colorful.

The eight animations badged new above are the SVG twins of the <Spinner> variants. Each contains exactly one color, so a single entry restyles the whole animation. The older spinners hold more — two in loadingDoubleRing, loadingFloatingSearch, loadingOrbital, loadingReloadArrow and loadingRipple, three in loadingPulseBars and loadingRedBlueBall, six in loadingPacman, eight in loadingSpinner, ten in loadingEllipsis, twelve in loadingSpinnerOval and thirty-two in loadingSquareBlocks — and take that many entries to recolor completely.

Use the Theme row in the gallery to load a ready-made palette, then Add color to extend it. The copied call always matches whatever the swatch row holds:

loadingDoubleRing({ size: 64, colors: ['#7c3aed', '#ec4899'] });

// Three entries against six colors: the first three are replaced, the rest stay as drawn.
loadingPacman({ size: 64, colors: ['#0099e5', '#38bdf8', '#a5f3fc'] });
SVG export<Spinner> variant
loadingSpokesdefault
loadingCircleNotchcircle
loadingPinwheelpinwheel
loadingCircleTrackcircle-filled
loadingDotsBounceellipsis
loadingPulseRingring
loadingEqualizerBarsbars
loadingInfiniteDashinfinite

Adding your own

Drop an .svg file into packages/loading-animations/src/svg/ and regenerate the barrel — the export name is the filename in camelCase, and this page picks it up automatically:

npm run make:icons

Terminal spinners

For CLI progress there is one more collection, of Unicode frame data, used by grab-url-cli:

import { dots, bouncingBar } from 'loading-animations';

const frames = [...dots]; // ["⠋", "⠙", "⠹", …]

Each entry is either a plain string (one character per frame) or a [string, n] tuple where n is the character width of each frame.

Last updated on

On this page