Loader Generator
Spinners, dots, bars and pulses as pure CSS — tuned on sliders, seeded from the 35 loaders already in the catalog, and emitted with the reduced-motion guard and the status role that hand-written ones go without
Ring
The default spinner: a border with one coloured side, rotated. Four declarations, works everywhere, and the one people recognise as "loading".
<div class="loader" role="status"><span class="loader-label">Loading…</span></div><div className="loader" role="status"><span className="loader-label">Loading…</span></div>.loader {
position: relative;
display: inline-block;
width: 40px;
height: 40px;
border: 4px solid rgba(79, 70, 229, 0.2);
border-top-color: #4f46e5;
border-radius: 50%;
animation: loader-anim 1s linear infinite;
}
@keyframes loader-anim {
to { transform: rotate(360deg); }
}
.loader-label {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
.loader,
.loader::before,
.loader::after,
.loader *,
.loader *::before,
.loader *::after {
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
animation-delay: 0ms !important;
transition-duration: 1ms !important;
transition-delay: 0ms !important;
}
}What ships with it
- A reduced-motion guard. Every loader here animates forever, which is exactly what
prefers-reduced-motionexists to stop. The guard collapses the duration rather than settinganimation: none, so the loader ends on its final keyframe instead of vanishing. - A status role and a name.
role="status"plus a visually-hidden “Loading…”, so the interval where sighted users are told to wait is not silence for everyone else. - Keyframes named after your class.
loader-anim. Keyframe names are global — two loaders sharing one means the second stylesheet silently redefines the first.
Start from one of the 42 in the catalog
See them allThese are the real entries, running. Picking one reads its own stylesheet — size, thickness, duration, colour, how many parts it has — and opens the sliders on those numbers. It lands on the nearest family rather than reproducing the original exactly; for the exact CSS, open the effect.
Now use it on something
Every effect, block, page and template in the catalog is styled against tokens shaped exactly like these — so they take your values rather than fighting them.
npx shadcn@latest add https://hoverlab.dev/r/hoverlab.json
One number sizes the whole thing — dots, orbit radius and stroke inset are all derived from it. A loader with nine independent measurements is one nobody gets right.
Border, stroke or bar width. Below about 2px a spinner disappears on a low-DPI screen; above about a tenth of the size it stops reading as a ring.
One full cycle. Under about 0.6s a spinner reads as frantic and makes a wait feel broken; over about 2s it reads as stalled.
A full CSS colour rather than a picker, because this one is almost always translucent — a track at 20% of the loader colour sits correctly on any surface, which an opaque grey does not.
Everything is namespaced to it, keyframes included, so two loaders from this tool can live in one stylesheet.
What a screen reader says when this appears. Name the operation — “Saving changes” beats “Loading”.
Keep this loader. This browser remembers what you last had open. A free account remembers the ones you name, on every machine you use.
- Keyframes EditorAuthor a multi-stop animation on a timeline instead of hand-typing percentages. Transform, opacity and blur per stop, playback controls, and the prefers-reduced-motion guard written for you — with the stricter guard when the animation loops.
- Grid GeneratorPaint a page layout onto a grid and copy the CSS, with grid-template-areas checked for the rectangle rule as you go — the rule that silently voids the whole declaration when you break it. Explicit tracks or a responsive auto-fit wall.
- Flexbox PlaygroundEvery flex container and flex item property on one screen, against a live row you can add to and resize. Shows the three-part flex shorthand as the browser actually resolves it, including the basis-versus-width trap.