A CSS loading spinner is one of the most common UI elements in modern interfaces, appearing on submit buttons during form processing, inside content cards during data fetches, and as full-page overlays during initial application loads.
Loading Animation Builder…
Size controls from 16px inline to 80px full-page
Colour picker for the active spinner arc
Adjustable stroke width and animation speed
Generates complete circle, border, and @keyframes rotate code
Drop the Animation Builder into any page — blog post, product docs, intranet, school portal — with a single line of HTML. Your visitors get the full tool, processed entirely in their browser. No backend, no uploads, no signup.
Embed code
<iframe
src="https://www.fixtools.io/css-tool/animation-builder?embed=1"
width="100%"
height="780"
frameborder="0"
style="border:0;border-radius:16px;max-width:900px;"
title="Animation Builder by FixTools"
loading="lazy"
allow="clipboard-write"
></iframe>Attribution-friendly: a small "Powered by FixTools" link appears in the embed footer.
A CSS spinner works through a combination of border-radius: 50%, which turns a square element with equal width and height into a circle, and the border transparency trick: apply a solid coloured border on one of the four sides and a low-opacity or transparent border on the other three sides, then rotate the entire circle infinitely using a CSS animation. Because the element is perfectly circular due to the border-radius, the single coloured border arc appears to move continuously around the circumference as the element rotates, creating the illusion of a spinning loading indicator. The rotate @keyframes rule is the simplest possible animation: transform: rotate(0deg) at the 0 percent stop and transform: rotate(360deg) at the 100 percent stop, with animation-timing-function set to linear so the spin speed is constant throughout each revolution. No JavaScript is needed at any point in this approach, and the spinner works even when scripts are blocked or still loading.
Sizing and colour choices determine how the spinner communicates context to the user and matches the visual weight of the surrounding interface. Small spinners at 16 to 20 pixels work inside buttons as inline loading indicators, replacing button text during an async submit or save action. Medium spinners at 32 to 48 pixels work inside card or section placeholders, indicating that content within a specific container is loading rather than the whole page. Large spinners at 64 to 80 pixels work as full-page loading overlays when the entire application state is being initialized or when a large data fetch is in progress. The coloured arc should contrast with both the page background and the surrounding element colour to remain visible across themes. The remaining three border sides should be set to a low-opacity version of the arc colour, typically 15 to 25 percent opacity, rather than fully transparent, so the circular track is subtly visible behind the moving arc.
Accessibility requires two additions beyond the visual CSS that take under a minute to add but make the loading state communicated to all users including those who cannot see the spinning arc. Add role="status" to the spinner element so screen readers announce that a loading state is in progress when the spinner appears in the DOM. Add aria-label="Loading" to give the announcement meaningful text content, since a bare spinner element with no inner text would otherwise be announced as an empty element by some screen readers. If the spinner is inside a region that will update with new content when loading completes, add aria-live="polite" to the container so screen readers also announce when the new content has arrived. Together these three attributes ensure the loading state is communicated through both visual and assistive channels.
Performance for an indefinite spinner depends on which CSS properties the animation touches. A rotate animation using transform: rotate() runs entirely on the GPU compositor thread, so even a spinner running continuously for thirty seconds has effectively zero ongoing performance cost on modern devices. The page can render other elements, accept clicks, and handle network responses while the spinner spins, with no impact on main thread work. Avoid implementing spinners using animations on background-color, box-shadow, or width, all of which trigger paint or layout on every frame and accumulate cost over long-running animations. The single border arc combined with transform: rotate() is the compositor-safe pattern, and it scales to dozens of simultaneous spinners on the same page without performance degradation.
Set the spinner diameter, choose the arc colour and background colour of the remaining border, then set animation duration. The preview spins with your settings applied. Copy the code when the spinner looks right.
Step-by-step guide to css loading spinner css:
Set spinner size
Enter the diameter in pixels. Use 18px for button spinners, 40px for card-level placeholders, and 64px for full-page loading overlays.
Choose arc and track colours
Set the arc colour to the primary brand colour or a high-contrast indicator colour. Set the track border to the same colour at 15 to 20% opacity for a visible circular guide.
Set stroke width and speed
Set the border-width to 10 to 15% of the diameter for balanced proportions. Set animation-duration to 0.7s for fast, 1s for standard, and 1.5s for slow and deliberate.
Copy the CSS and add ARIA attributes
Click Copy Code and paste the spinner CSS into your stylesheet. Add role="status" and aria-label="Loading" to the HTML element for accessibility.
Common situations where this approach makes a real difference:
Form submit button loading state
A checkout form developer replaces the Submit button text with a 20px spinner when the payment is processing. The button is disabled immediately on click to prevent duplicate submissions. The spinner inherits the button text colour, maintaining the button's visual style. On success or failure, the spinner is replaced with the appropriate message and the button is re-enabled or left disabled based on the outcome.
Dashboard card content placeholder
A SaaS analytics dashboard shows 40px spinners inside each metric card while the API data loads. Each card has a fixed minimum height set so the layout does not reflow when the spinner is replaced by the actual data. The spinners use the dashboard's primary blue on a light grey track, matching the brand palette. All spinners disappear together when the batch fetch resolves.
Full-page application loading screen
A single-page application shows a 64px spinner centred on a white overlay during the initial JavaScript bundle load and authentication check. The overlay uses position: fixed with a high z-index to sit above all content. Once the app state is ready, the overlay fades out with a 200ms opacity transition before being removed from the DOM so the transition is smooth rather than a hard cut.
Infinite scroll list loader
A content feed triggers a next-page fetch when the user scrolls near the bottom. A 32px spinner appears at the end of the list during the fetch. The spinner uses a muted colour so it does not visually compete with the content above it. On fetch completion, the spinner is removed and the new items are appended. If the fetch returns an empty array, the spinner is replaced with an "All items loaded" message.
Use this when you need a pure CSS spinner for a button loading state, a full-page overlay, or an inline content placeholder, and want to skip writing the border and keyframe setup by hand.
Get better results with these expert suggestions:
Use a CSS custom property for spinner size to resize in one edit
Set --spinner-size on the element and use it for width, height, and border-width together. Resizing then requires one variable change. Without this pattern, adjusting spinner size means hunting across three separate property values in the declaration.
Set border-color with alpha for the track, not a lighter hue
Use the same colour as the active arc but at 15 to 20% opacity for the three non-active border sides. This ensures the track colour adjusts automatically if the arc colour changes, without needing a separate colour value to maintain. A separate light hue often drifts from the brand colour over time as the design evolves.
Lock button min-width before replacing text with a spinner
Read the button's offsetWidth in JavaScript and set it as a style.minWidth before swapping the text for a spinner. This prevents layout shift when the spinner is narrower than the original button label. Remove the inline style after the loading state ends.
Test spinner animation under CPU throttle to catch jank
Open DevTools Performance panel and set CPU slowdown to 6x. A CSS rotate animation should stay smooth since transform runs on the compositor thread. If the spinner stutters under throttle, check whether any layout-triggering styles are being applied to the spinner element or its parent during the loading state.
More use-case guides for the same tool:
Open the full Animation Builder — free, no account needed, works on any device.
Open Animation Builder →Free · No account needed · Works on any device