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.
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.
ring · dots · pulse · ripple · text · bar · ring-meter · unibar.
Determinate bars & rings via value; set el.value from JS.
Braille/emoji spinners and pip/wget/curl-style text bars.
Every knob is a CSS variable. Gradients and rainbow built in.
One shared stylesheet for every instance; nothing leaks.
role="progressbar" + ARIA; respects reduced-motion; pauses off-screen.
Pick a CDN tag or install from npm. The script auto-registers
<just-a-moment> on load — there's nothing to initialize.
<!-- unpkg, latest (minified) -->
<script src="https://unpkg.com/just-a-moment"></script>
npm install just-a-moment # or: bun add just-a-moment
// side-effect import registers <just-a-moment> import 'just-a-moment';
</> to see its exact markup.Set type (or a preset) and tweak with color,
size, speed. color="rainbow" and CSS gradients just work.
A cycled glyph for any frame set. Override speed with interval.
Pass value for determinate, omit it (or set indeterminate)
for the loading state. rounded, striped and label are opt-in.
The kind pip, wget and cargo print — type="unibar" with a
variant of block, pip, wget, curl or arrow.
type="ring-meter". Conic by default; caps="round" draws an
SVG ring with rounded ends. Add label for a centered %.
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>
JustAMoment.frames('weather', '☀️,⛅,☁️,🌧️', 350); <just-a-moment type="text" frames="weather" size="30"></just-a-moment>
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>
JustAMoment.preset('upload', { type: 'unibar', variant: 'wget', color: '#7dcfff' }); <just-a-moment preset="upload" value="64"></just-a-moment>
just-a-moment.neon{ --color:#39ff14; --track:#0a2a04; --size:46px; --thick:6px; } <just-a-moment class="neon"></just-a-moment>
color
<just-a-moment color="conic-gradient(from 0deg,#f7768e,#7aa2f7,#f7768e)" size="46"></just-a-moment>
JustAMoment.register('my-loader')
JustAMoment.register('my-loader'); <my-loader type="dots" count="5" color="#e0af68"></my-loader>
<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).type)| type | What | Determinate | Variants |
|---|---|---|---|
ring (default) | Rotating ring | — | dual; gradient/rainbow color |
dots | A row of dots | — | bounce, pulse, fade; count |
pulse | Breathing disc | — | — |
ripple | Expanding rings | — | — |
text | Cycled unicode/emoji glyph | — | any frames; interval |
bar | Linear progress bar | ✓ | striped; rounded |
ring-meter | Circular gauge | ✓ | caps="round" |
unibar | Terminal-style text bar | ✓ | block, pip, wget, curl, arrow |
| Attribute | What it does |
|---|---|
type preset variant | Engine, named look, engine variant. |
color | A color, any CSS gradient(…), or rainbow. |
size | Diameter / glyph size (bare number → px). |
speed | Animation duration (.8s; bare number → ms). |
thickness track | Ring/track thickness and the unfilled track color. |
width height | Bar dimensions. |
radius / rounded | Corner radius; rounded = fully round ends. |
count | Number of dots. |
frames interval | Text engine: frame set + ms per frame. |
value max | Determinate progress (bar / ring-meter / unibar). |
indeterminate | Force the animated loading state. |
caps | round → SVG ring-meter with rounded ends. |
label | Show a % label (bar / ring-meter). |
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.
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
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');
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){}