A pure-CSS ring spinner produced by the CSS Loader Generator is a single square element with equal width and height, a full circular border-radius, a track color applied to the whole border, and an active color overriding one segment through border-top-color. The generator emits two short blocks of CSS: a .loader class that carries every visual property the live preview uses, and a uniquely named @keyframes rule that rotates the element through 360 degrees with a linear, infinite animation declaration. No SVG file, no GIF, no image asset, and no JavaScript animation loop is required; the geometry stays crisp at every pixel density because everything is drawn by the browser's CSS renderer, and the runtime cost is just one transform operation per frame. The CSS Loader Generator lets you set the size, ring width, track color, active color, and duration with bounded numeric inputs and strict 6-digit hex colors, then copy a baseline that drops directly into any stylesheet. The preview is not a mockup either; it is the actual status element using the same dimensions, border, color, and timing values that will be serialized into the output.

What the Generator Builds
The generator exposes the smallest useful set of controls and refuses to do anything more. Each control maps to a single CSS property on the .loader rule, and the live preview is the same element with the same dimensions, border, color, and timing values that get serialized when you copy. This is why the preview is trustworthy as a sizing reference: if it looks right in the preview, it will look the same way in production, modulo the surrounding theme.
Five controls, no more. Size sets the box width and height in pixels. Ring width sets border-width on the box. Track color sets border-color, the duller tone that fills the full ring. Active color sets border-top-color, the brighter segment that appears to travel around the rim. Duration feeds the animation shorthand. Everything else, including border-style: solid, border-radius: 50%, animation-timing-function: linear, and animation-iteration-count: infinite, is fixed by the generator so the output is predictable and reproducible across browsers.
The technique is deliberately cheap. A square box with equal width and height becomes a circle when border-radius reaches fifty percent. Filling the whole border with one color and overriding a single side with another creates the illusion of a rotating arc without any masks, gradients, or pseudo-elements. A keyframe that simply rotates the box to 360 degrees gives infinite motion with one property. Because only transform-style properties are animated, the browser can hand the rotation off to the compositor and avoid layout and paint on every frame.
Input Cheat Sheet
The numeric inputs and colors are protected by strict bounds. Empty fields are treated as invalid rather than silently coerced to zero, and non-finite values are rejected outright, so the preview never renders a broken layout because of a typo. The table below summarizes the operating range of every input the generator accepts and the property each one drives in the output CSS.
| Input | Output CSS property | Format | Minimum | Maximum | Note |
|---|---|---|---|---|---|
| Size | width, height | integer pixels | 12 | 160 | Equal sides keep the ring centered |
| Ring width | border-width | integer pixels | 1 | size / 2 | Above half the size the visible opening collapses |
| Duration | animation-duration | decimal seconds | 0.2 | 5 | Below 0.2 s feels jittery; above 5 s looks frozen |
| Track color | border-color | 6-digit hex | n/a | n/a | Fills the full ring |
| Active color | border-top-color | 6-digit hex | n/a | n/a | Overrides one segment of the ring |
If you need values outside these bounds, edit the copied CSS directly instead of trying to bypass the form. The bounds exist because the geometry only behaves like a ring inside that range: a border thicker than half the box's shortest side inverts the visual center and no longer reads as a clean opening, and durations outside the 0.2–5 s window either feel uncomfortable or look stalled. The strict 6-digit hex format is enforced so the generator can serialize a color it can also render in the preview, including in dark themes where short-form 3-digit hex and named colors can produce subtle inconsistencies.
Build a Ring Spinner in the Generator
The procedure below covers the full flow inside the tool, from picking proportions to copying a usable baseline. Treat the preview as the source of truth: because the values are passed through unchanged into the output CSS, what looks right in the preview will look the same in your stylesheet.
- Pick a size that matches the surrounding layout. Decide ring width first so you can see how thick the active segment should be, then size the box so the ring stays comfortably under half the box's side; for example, a 48 px box with a 6 px ring keeps a 36 px inner opening that reads as a clear hole on small screens.
- Pick a contrasting pair of colors. Use a muted tone for the track and a saturated tone for the active segment. Test both colors against the lightest and darkest surfaces in your product, because the same pair can read as low contrast in a dark theme even when it is clearly distinguishable in a light theme.
- Set the duration while watching the live rotation. Move the slider until the spin feels informative rather than distracting; operations that take a second or two usually pair well with a 1 s rotation, while long-running background work looks better with a slower 1.5–2 s rotation that draws less of the viewer's attention.
- Copy the CSS, paste it into your stylesheet, and rename the .loader class and the @keyframes rule if your stylesheet already defines either identifier; the generator does not detect name collisions.
Anatomy of the Copied CSS
The output is intentionally short. It contains exactly what the preview rendered, plus a keyframes rule that is unique enough to be safe in most stylesheets. Together the two blocks form a baseline you can extend without rewriting the geometry.
| Block | Selector | Purpose |
|---|---|---|
| Style rule | .loader | Carries size, ring width, both colors, and the animation shorthand |
| Keyframes | (uniquely named) | Rotates the element from 0 to 360 degrees |
The animation shorthand is assembled from the duration you set, plus the fixed linear timing function and infinite iteration count. If you need a different visual rhythm, swap border-top-color for border-right-color or border-left-color to shift the active segment around the rim, or change border-style to dashed for a different texture without touching the geometry. To produce multiple spinners with different scales, duplicate the .loader rule under a different class name and adjust the size and ring width; both blocks can share the same keyframes rule since rotation is rotation.
Turn the Spinner into a Real Loading State
A spinner is only useful when it appears at the right moment, with the right text, and disappears on completion. The generator does not manage state for you; that responsibility belongs to the surrounding application, and the baseline output should be wrapped in a container that mounts the .loader element when a request starts and removes it when the request resolves, rejects, or times out.
Use a stable container that reserves space before the spinner mounts so the rest of the layout does not shift when the element appears or disappears. If the operation's progress can be measured, prefer a progress bar with value over an indeterminate spinner; the spinner is best reserved for work whose progress cannot be tracked. Never let an animated loader stand in for a failure: expose a clear timeout, a cancel control, a retry button, and a textual error message so users always know whether the operation is still running or has stalled.
In practice this means treating the copied .loader class as a leaf visual, not as a complete loading UI. The application owns the timing of when to mount the element, when to swap it for results, and when to replace it with an error state. The preview only validates that the visual itself looks correct; it does not validate that the loading state has any lifecycle at all.
Accessibility and Reduced Motion
Accessibility is not part of the copied baseline because each product has its own policy. Add three things to every production spinner: a meaningful accessible name, a status region that announces changes politely, and a reduced-motion override that respects user preferences.
The live preview uses role="status" and a generic aria-label so the element announces itself to assistive technology. Replace that label with a task-specific phrase such as "Loading search results" or "Uploading file" so screen reader users hear what is actually happening. Do not announce rapid minor updates; an aria-live="polite" region with stable text is usually enough, and "assertive" should be reserved for messages the user has to act on immediately.
Continuous rotation can trigger vestibular discomfort for some users. Add a @media (prefers-reduced-motion: reduce) block that either pauses the animation, slows it dramatically, or replaces the rotating element with a static indicator. The W3C CSS Animations Level 1 specification defines how animation-name, animation-duration, and animation-iteration-count behave inside this media query, and the MDN Using CSS animations guide walks through the same patterns with worked examples. Test the same rule under forced-colors mode, dark theme, browser zoom, and a small viewport before shipping.
Performance and Lifecycle Rules
A single ring spinner is cheap because transform: rotate() can be composited by the browser without triggering layout or paint on every frame. Many simultaneous spinners still cost CPU and create visual noise, so render the element only where feedback is actually required and remove it as soon as the operation finishes.
Test the loader against real network latency, not just a permanent demo loop. Confirm that the element disappears on success, on cancellation, and on error, and confirm that it does not linger when the user navigates away. Rename the .loader class and the @keyframes identifier if your stylesheet already defines either, because the generator does not detect name collisions. If you need several spinners with different scales or colors in the same project, repeat the copied rule under different class names and adjust size and ring width per instance.
All generation happens locally in the browser: settings are not uploaded, no account is required, and no dependency is added to your project. Treat the copied baseline as a starting point, then layer meaningful status text, reduced-motion behavior, failure handling, and lifecycle logic on top of it before shipping.
If you're weighing options, CSS Speech Bubble Generator: Command Line vs Online covers this in detail.
If you're weighing options, CSS Stripes Generator Cheat Sheet: Angle, Widths, Output covers this in detail.