How to Build a CSS Loader: Spinners, Dots, Bars, Pulses, and UX Tips
A CSS loader is a small animation that tells users something is loading. This article explains, step by step, how to build a circular spinner with border and border-radius, spin it with @keyframes, create dot, bar, and pulse variations, respect accessibility with prefers-reduced-motion, and decide when a loader should appear.
While waiting for data to load on a web page or app, you have surely seen a small spinning animation. That is a CSS loader (spinner). It can be built lightly with CSS alone, using no images at all, and simply showing it reassures users that "things are working." This article walks through how to build the most basic rotating spinner, variations such as dots, bars, and pulses, and accessibility considerations for people who are sensitive to motion.
What Is a CSS Loader
A CSS loader is an animation that visually tells the user "loading now" while a process or request finishes. The rotating circular spinner is the most famous, but there are many shapes: dot types where points light up in turn, bar types where bars stretch and shrink, and pulse types that softly grow and fade.
The big advantage of a CSS loader is that it can be done entirely in CSS, without images or JavaScript. Unlike a GIF, it takes no file size, stays sharp when enlarged, and you can freely adjust color and size just by changing CSS values. Even in slow environments, the loader itself renders lightly, so it helps ease the stress of "being kept waiting."
Start by switching between a few styles in the CSS Loader Generator to see how they move; it makes the mechanisms below easier to picture. Change the shape and speed, and check how the generated code corresponds to the look.
The Basics: A Spinner with border and border-radius
The classic circular spinner can be built with just one element and a few lines of CSS. The idea is simple: make a square element round with border-radius to form a circle, then color only one of the four border sides differently.
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e0e0e0;
border-top-color: #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
Let us look at the key points in order.
- Make width and height equal: a square becomes a perfect circle once border-radius is applied.
- Draw a thin, light border around the whole element: this becomes the rotating circle's "track" base.
- Color only border-top-color darker: changing just one side creates the indicator that shows rotation.
- Make a circle with border-radius: 50%: the square's border turns into an arc, giving a ring-like look.
At this point it is still a static image: you only have a ring with one differently colored side, and it does not rotate. Making it move is the job of @keyframes, explained next. When you want to change color or size, moving the sliders in the CSS Loader Generator while watching the effect speeds up understanding.
How @keyframes Makes It Rotate
To spin the static ring, you use @keyframes, the heart of CSS animation. @keyframes defines "how an element's style changes from the start to the end of an animation." For a spinner, a simple definition that rotates the element once is enough.
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
This says: no rotation at from (0%), and a 360-degree rotation at to (100%). The browser automatically interpolates the angles in between, so it makes a smooth single turn. What ties this @keyframes to the element is the animation property from earlier.
- spin: the name of the
@keyframesto play. - 1s: the time for one rotation. Shorter spins faster, longer spins slower.
- linear: how the speed changes. linear feels natural for a constant-speed rotation.
- infinite: how many times to repeat. Since it should keep spinning while loading, use infinite.
The key thing to remember for a spinner is to always express movement with transform: rotate(). Because transform can render without recalculating layout, it is smoother and lighter than animating margin or positional properties. When you want to experiment with building @keyframes in more detail, editing the style at each step in the CSS Keyframes Generator and checking the output helps you grasp interpolation.
Building Dot, Bar, and Pulse Loaders
Besides the rotating spinner, there are several classic loaders. Once you understand the idea, each can be built as an application of @keyframes.
1. Dot type (points light up in turn)
Line up three small circles and give each the same "grow/shrink" or "opacity change" animation. The trick is to stagger the start with animation-delay. Delay the first by 0s, the second by 0.2s, and the third by 0.4s, and the dots appear to light up one after another in a wave.
2. Bar type (bars stretch and shrink)
Line up several thin vertical bars and stretch them along the height with transform: scaleY(). Just like the dot type, staggering animation-delay creates a waving motion like an equalizer. Using scaleY also lets it move smoothly without recalculating layout.
3. Pulse type (softly grows and fades)
Enlarge a single circle with transform: scale() while lowering opacity so it appears to fade out. Repeating "grow and fade" like a heartbeat makes a loader that quietly asserts its presence.
All of these are combinations of the same patterns: "multiple elements plus delay" or "scale plus opacity." Before writing by hand, switch between the types in the CSS Loader Generator and compare the generated code so you intuitively see which value does what.
Respecting Motion with prefers-reduced-motion
A constantly spinning animation is convenient, but it can be a burden for people who are sensitive to strong motion or who have conditions akin to dizziness or motion sickness (vestibular disorders). This is where the media query prefers-reduced-motion, which detects OS settings like "reduce motion," comes in handy.
@media (prefers-reduced-motion: reduce) {
.spinner {
animation: none;
}
}
This stops the rotation only when the user has chosen a "reduce motion" setting. But removing it entirely means "loading" is no longer communicated, so it is kinder to show a static ring or "Loading..." text instead of stopping the animation completely.
- Provide an alternative, not just a stop: keep conveying state with a static circle or progress text.
- Communicate state to assistive technology too: adding
role="status"oraria-label="Loading"to the loader element tells screen reader users the situation.
Accessibility is not "for a special few"; it leads to a UI that is clearer for everyone. It also helps to keep the motion gentle while editing the @keyframes; preparing a smaller-change version in the CSS Keyframes Generator makes it easy to reuse as the reduced-motion alternative.
Timing: When to Show a Loader
A loader is not something you should "just show anyway." Showing it the wrong way or at the wrong time can make things feel slower instead. Finally, let us organize the points to watch from a UX perspective.
| Situation | Recommended approach |
|---|---|
| A process that finishes instantly | Do not show one at all (it just flickers) |
| A wait of about 0.3 to 1 second | A simple spinner is enough |
| A wait of several seconds or more | Add a progress bar or estimated time |
Be especially careful of the "loader that appears for only an instant." If you show a loader when a process finishes in tens of milliseconds, the screen just flashes, which actually gives an unstable impression. To prevent this, a useful trick is delayed display: only show the loader once loading exceeds a set time (for example, 300 milliseconds).
Also, when the wait is long, showing a progress percentage or processing stage is more reassuring than a spinner that just keeps turning. Simply knowing "almost there" greatly shrinks the perceived wait. Depending on the content, it is also effective to combine a "skeleton screen" that shows the content's frame first.
Once you grasp the basics of spinning a circle made with border and border-radius using @keyframes, you can freely extend it to dots, bars, and pulses. Start by finding a shape you like in the CSS Loader Generator, and fine-tune the motion as needed with the CSS Keyframes Generator. Moving your hands and seeing the mechanism with your own eyes is the shortcut to understanding.
Please note that this article is intended to provide general information. CSS specifications, browser behavior, and accessibility guidelines can change, so when implementing, make your decision after checking the latest official documentation.
FAQ
Can I build a loader (spinner) with CSS alone?
Yes. You can build one with CSS alone, no images or JavaScript needed. Make a square element a circle with border-radius: 50%, color only border-top-color differently, and spin it with transform: rotate() inside @keyframes, and you get a lightweight rotating spinner. Color and size can be freely adjusted just by changing CSS values.
What is the difference between @keyframes and animation?
@keyframes is the blueprint that defines "how the style changes from the start to the end of an animation." The animation property, on the other hand, actually applies that @keyframes to an element and sets the duration, speed, repeat count, and so on. They are used together: think of @keyframes as the content of the motion and animation as the playback settings.
Why does my loader move choppily instead of smoothly?
If you move position or size with margin or width, the browser recalculates layout and it tends to stutter. Express rotation and scaling with transform (rotate or scale), and fading with opacity, to avoid recalculation and render smoothly. If the animation duration is too short it also looks frantic, so aim for around one second.
How should I accommodate people sensitive to animation?
Use the media query @media (prefers-reduced-motion: reduce) to stop the animation for users who have enabled "reduce motion" in their OS. Since removing it entirely hides the state, show a static ring or "Loading..." text instead, and convey the situation to assistive technology with role="status" and similar attributes.
Is the code created in the CSS Loader Generator sent to a server?
WAZA TOOLS' CSS Loader Generator and CSS Keyframes Generator run entirely in your browser, so your input and the generated code are never sent to or stored on a server. They work on smartphones too, and you can generate and copy code as many times as you like, for free.