one <script> · zero config · ~5.6 KB min+gzip

A loading spinner
is just <just-a-moment>

Spinners, progress bars and progress rings as one tiny custom element — CSS, unicode and terminal-style, determinate or not, isolated in its own Shadow DOM. Pick a look with an attribute. Drive progress with el.value.

Get started ★ GitHub

Why just-a-moment

Copy-paste CSS loaders are fiddly to theme, hard to make determinate, and rarely accessible. This is one element that covers spinners, bars and rings — and it's just markup.

Eight engines

ring · dots · pulse · ripple · text · bar · ring-meter · unibar.

Real progress

Determinate bars & rings via value; set el.value from JS.

Unicode & terminal

Braille/emoji spinners and pip/wget/curl-style text bars.

Themeable

Every knob is a CSS variable. Gradients and rainbow built in.

Shadow-DOM isolated

One shared stylesheet for every instance; nothing leaks.

Accessible

role="progressbar" + ARIA; respects reduced-motion; pauses off-screen.

Install

Pick a CDN tag or install from npm. The script auto-registers <just-a-moment> on load — there's nothing to initialize.

CDN (fastest)

<!-- unpkg, latest (minified) -->
<script src="https://unpkg.com/just-a-moment"></script>

npm

npm install just-a-moment   # or: bun add just-a-moment
// side-effect import registers <just-a-moment>
import 'just-a-moment';
Hover any example below and click </> to see its exact markup.

Spinners

Set type (or a preset) and tweak with color, size, speed. color="rainbow" and CSS gradients just work.

default ring
dual
rainbow
gradient
dots
dots-pulse
dots-fade
pulse
ripple

Unicode & emoji

A cycled glyph for any frame set. Override speed with interval.

braille
line
arc
bar
arrow
moon
clock
earth

Progress bars

Pass value for determinate, omit it (or set indeterminate) for the loading state. rounded, striped and label are opt-in.

driven by el.value

Unicode / terminal progress bars

The kind pip, wget and cargo print — type="unibar" with a variant of block, pip, wget, curl or arrow.

Progress rings

type="ring-meter". Conic by default; caps="round" draws an SVG ring with rounded ends. Add label for a centered %.

conic
round caps
90%
indeterminate
indeterminate

Extend it

No build, no fork. Load the script, then add your own frame sets, presets and tags at runtime with a few lines — each example below shows the code and its live output. Register before the elements render (e.g. create them after, as these do).

1 · A custom unicode frame set — JustAMoment.frames(name, glyphs, msPerFrame)
JustAMoment.frames('pingpong', '▖▘▝▗', 120);

<!-- then, anywhere -->
<just-a-moment type="text" frames="pingpong" color="#9ece6a"></just-a-moment>
output
2 · Emoji frames — comma-separate multi-glyph frames
JustAMoment.frames('weather', '☀️,⛅,☁️,🌧️', 350);

<just-a-moment type="text" frames="weather" size="30"></just-a-moment>
output
3 · A named preset — JustAMoment.preset(name, {…}) bundles a look
JustAMoment.preset('brand', {
  type: 'ring', color: 'rainbow', size: '52px', speed: '.7s'
});

<just-a-moment preset="brand"></just-a-moment>
output
4 · A preset on any engine — reuse a progress style by name
JustAMoment.preset('upload', {
  type: 'unibar', variant: 'wget', color: '#7dcfff'
});

<just-a-moment preset="upload" value="64"></just-a-moment>
output
5 · Theme with plain CSS variables — no JS at all
just-a-moment.neon{
  --color:#39ff14; --track:#0a2a04;
  --size:46px; --thick:6px;
}

<just-a-moment class="neon"></just-a-moment>
output
6 · A gradient color inline — any CSS gradient works as color
<just-a-moment
  color="conic-gradient(from 0deg,#f7768e,#7aa2f7,#f7768e)"
  size="46"></just-a-moment>
output
7 · Your own tag — JustAMoment.register('my-loader')
JustAMoment.register('my-loader');

<my-loader type="dots" count="5" color="#e0af68"></my-loader>
output
Why create the elements after registering? The script defines <just-a-moment> and upgrades any already on the page the moment it loads. Custom frames/presets/tags must exist before an element using them is built — so register in <head>, or add the elements afterward (the examples above are injected after registration).

Reference

Engines (type)

typeWhatDeterminateVariants
ring (default)Rotating ringdual; gradient/rainbow color
dotsA row of dotsbounce, pulse, fade; count
pulseBreathing disc
rippleExpanding rings
textCycled unicode/emoji glyphany frames; interval
barLinear progress barstriped; rounded
ring-meterCircular gaugecaps="round"
unibarTerminal-style text barblock, pip, wget, curl, arrow

Attributes

AttributeWhat it does
type preset variantEngine, named look, engine variant.
colorA color, any CSS gradient(…), or rainbow.
sizeDiameter / glyph size (bare number → px).
speedAnimation duration (.8s; bare number → ms).
thickness trackRing/track thickness and the unfilled track color.
width heightBar dimensions.
radius / roundedCorner radius; rounded = fully round ends.
countNumber of dots.
frames intervalText engine: frame set + ms per frame.
value maxDeterminate progress (bar / ring-meter / unibar).
indeterminateForce the animated loading state.
capsround → SVG ring-meter with rounded ends.
labelShow a % label (bar / ring-meter).

Frame sets & presets

Frames: dots (braille), line, arc, circle, square, triangle, arrow, bounce, bar, star, toggle, moon, earth, clock.

Presets: ring, dual, rainbow, gradient, dots, dots-pulse, dots-fade, pulse, ripple, braille, line, arc, clock, moon, bar, bar-striped, meter, unibar, unibar-pip, unibar-wget.

Dev guide

TypeScript, one engine per file under src/engines/, bundled with Vite and end-to-end tested with Playwright.

git clone https://github.com/i-rocky/just-a-moment && cd just-a-moment
bun install
bun run dev            # demo at http://localhost:8000 (Vite)
bun run build          # dist: ESM + minified IIFE + .d.ts
bun run test:install && bun run test   # e2e + screenshots of every spinner

Drive progress & extend at runtime

const el = document.querySelector('just-a-moment');
el.value = 73;                       // determinate bars/rings

JustAMoment.preset('brand', { type: 'ring', color: 'rainbow', size: '56px' });
JustAMoment.frames('pingpong', '▖▘▝▗', 120);  // glyphs, ms/frame
JustAMoment.register('my-loader');

Theming

just-a-moment{ --color:#89b4fa; --track:#313244; --size:56px; --speed:.6s; }
just-a-moment::part(track){} just-a-moment::part(fill){} just-a-moment::part(label){}