A CSS loader generator for pure-CSS spinners
This CSS loader generator produces loading indicators built from a single element or a few empty spans, animated with @keyframes. There are no images, SVG files or JavaScript involved, so a spinner costs a few hundred bytes of CSS and renders sharply at any size and pixel density. Every loader animates transform (and opacity for the pulse), which browsers can usually run on the compositor without re-laying out the page.
You choose one of five styles, a color, a size and the length of one animation cycle. The markup is included in the HTML tab with role="status" and aria-label="Loading" already set.
Example: the default ring
.loader {
width: 48px;
height: 48px;
border: 5px solid rgba(15, 118, 110, 0.2);
border-top-color: #0f766e;
border-radius: 50%;
animation: loader-spin 1s linear infinite;
}
@keyframes loader-spin {
to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
.loader, .loader span { animation-duration: 3s; }
}
<div class="loader" role="status" aria-label="Loading"></div>
The ring is a circle with a faint track (the chosen color at 20% opacity) and one solid top border. Rotating the whole element makes the solid segment chase around the track.
The five styles and how they work
| Style | Markup | Technique |
|---|---|---|
| Ring | 1 element | Transparent-tinted border with a solid border-top-color, rotated linearly. |
| Dual ring | 1 element | border-color: c transparent c transparent gives two opposite arcs, rotated linearly. |
| Bouncing dots | 3 spans | Each dot scales from 0 to 1 and back; negative delays of about a third and a sixth of the cycle stagger them. |
| Bars | 4 spans | Bars scale vertically between 0.4 and 1 with staggered negative delays, like an equalizer. |
| Pulse | 1 element | A filled circle grows from scale 0 to 1 while fading from opacity 1 to 0. |
Negative animation-delay values start each dot or bar partway through its cycle, so the loader looks staggered from the very first frame instead of waiting for the delays to pass.
Options explained
- Colour – the main color; the ring's track is derived from it automatically.
- Size – the outer width and height. Dots are sized at size ÷ 3.5 with a gap of size ÷ 5; bars are size ÷ 7 wide, so everything scales together.
- Thickness – border width for Ring and Dual ring only (1–20px); the control is hidden for other styles.
- Duration of one cycle – 0.3 to 4 seconds. Around 0.8–1.2s feels active without looking frantic.
Accessibility built in
role="status" makes the loader a polite live region, so assistive technology can announce it without interrupting the user. Some screen readers announce a label on an empty element less reliably than real text, so for important waits put visually hidden text such as "Loading…" inside the element. Also mark the region that is updating with aria-busy="true" and remove it when content arrives.
The CSS ends with a prefers-reduced-motion query that slows every loader to a 3-second cycle for users who ask their operating system to reduce motion. The spinner still shows that something is happening, just without rapid movement. If you prefer, replace it with animation: none and a static "Loading…" label.
Tips and common mistakes
- Delay the spinner. Showing a loader for a 150ms request just causes a flash. Reveal it only if the wait passes about 300ms.
- Hide it properly. Remove the element or set the
hiddenattribute when loading ends; an invisible but running animation still uses CPU. - Check keyframe names. The generated names (
loader-spin,loader-bounce,loader-bars,loader-pulse) are global. Rename them if your stylesheet already uses those names. - Center it with the parent. The loader has no margins. Use
display: grid; place-items: centeron the container. - Mind contrast. A thin, light ring on a white page can be hard to see. Increase thickness or darken the color, and check it against the Dark preview background if your site has a dark theme.
CSS loader vs GIF or SVG spinner
An animated GIF is a separate request, cannot change color with your theme and looks blurry on high-density screens. An inline SVG spinner is flexible but longer to write. A pure CSS loader is the smallest option and can take its color from a CSS variable. For custom motion beyond these five styles, the CSS animation generator gives you keyframe presets, and the cubic-bezier generator lets you replace ease-in-out with your own timing curve.
Privacy
The loader code is generated locally in your browser. No settings or code are sent to a server.