webdevpuneet.com

Dots Loader — Free HTML CSS Loading Snippet

Dots Loader · Loaders · Plain HTML, CSS & JS · Live preview

CategoryLoaders

Copy & paste

Source Code

<div class="page">

  <div class="section">
    <h3 class="label">Bounce</h3>
    <div class="loader bounce">
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
    </div>
  </div>

  <div class="section">
    <h3 class="label">Pulse</h3>
    <div class="loader pulse">
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
    </div>
  </div>

  <div class="section">
    <h3 class="label">Wave</h3>
    <div class="loader wave">
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
    </div>
  </div>

  <div class="section">
    <h3 class="label">Elastic Spinner</h3>
    <div class="spinner">
      <div class="ring"></div>
    </div>
  </div>

  <div class="section">
    <h3 class="label">Button loading state</h3>
    <button class="btn-loading" onclick="triggerLoad(this)">
      <span class="btn-text">Save changes</span>
      <span class="btn-dots" aria-hidden="true">
        <span></span><span></span><span></span>
      </span>
    </button>
  </div>

</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Bounce: translateY(-14px) + opacity with 0.2s staggered animation-delay
Pulse: scale(0.6→1.2) + opacity on 4 dots with staggered delay
Wave: scaleY(0.3→1) on 5 bar-shaped dots — equaliser/waveform effect
Spinner: border-top-color arc + cubic-bezier(0.6,0,0.4,1) elastic timing
Button loading: .loading hides text, shows inline dots, disables re-click
Button success: removes .loading, shows ✓ Saved!, green background for 2s
All animations: transform + opacity only — GPU compositor, no layout triggers
Zero JavaScript for the four display variants — pure CSS keyframes

About this UI Snippet

Dots Loader — Bounce, Pulse, Wave, Spinner & Button Loading State — Pure CSS Animations

Screenshot of the Dots Loader snippet rendered live

Loading dots are the most versatile and universally recognised loading indicator pattern. Unlike a progress bar (which implies a known completion time) or a skeleton loader (which implies imminent content), loading dots communicate "something is happening" in a neutral, open-ended way. This snippet provides four distinct dot animation variants plus a button loading state — all animating with pure CSS keyframes and no JavaScript for the animations themselves.

Bounce uses translateY(-14px) at the 50% keyframe to lift each dot upward. Staggered animation-delay (0s, 0.2s, 0.4s) on the three dots creates the wave-bounce sequencing. Opacity fades from 0.4 to 1 in sync with the translation, giving the dots a depth illusion as if they are closer when raised.

Pulse scales each dot from scale(0.6) to scale(1.2) with four dots at 0.2s staggered delays. The scale oscillation plus opacity creates a breathing, organic feel. This variant works well for "thinking" or processing states where there is no known end time.

Wave uses bar-shaped dots (8×32px, border-radius: 4px) and scaleY animation — each bar shrinks to 30% height and grows back to full height in sequence, mimicking an audio waveform or equaliser. The five bars with 0.1s staggered delays create a smooth rolling wave.

Elastic Spinner uses border-top-color: #6366f1 on a full-circle border to create the classic arc spinner. The cubic-bezier(0.6, 0, 0.4, 1) timing function accelerates and decelerates the rotation to give an elastic, bouncy feel — much more polished than linear spin.

Button Loading State is the most practically useful variant. Clicking the button adds .loading which hides the text span and shows three inline pulse dots. After an async operation completes (replace the setTimeout with your real fetch call), the .loading class is removed and a success state is shown. The button becomes cursor: not-allowed during loading to prevent double-submission.

All animation variants use transform (translateY, scale, scaleY, rotate) and opacity — properties that run on the GPU compositor and never trigger layout recalculation, keeping all five animations at 60fps even on low-end devices.

Choosing the right variant

Bounce works best for full-page loading overlays and large loading states where the vertical movement reads clearly. Pulse is the standard for "thinking" or processing states — three pulse dots are universally recognised as "in progress". Wave is distinctive for audio-adjacent contexts (music players, voice recorders, chat transcription). The elastic spinner is the most compact and works well in button loading states, small inline indicators, and form field validation spinners.

Step by step

How to Use

  1. 1
    Pick your variantClick the "Save changes" button to see the button loading state. All four animation variants (bounce, pulse, wave, spinner) run automatically on page load.
  2. 2
    Copy just the variant you needCopy only the HTML, CSS, and JS for the specific variant you need. Each is self-contained — bounce needs only .bounce .dot CSS rules, pulse needs only .pulse .dot, and so on.
  3. 3
    Wire the button loading state to your formIn JS, replace the setTimeout inside triggerLoad() with your real fetch or form submission. Call btn.classList.remove("loading") inside .then() or .finally() so the button always re-enables even on error.
  4. 4
    Change the loader colourReplace #6366f1 in the CSS with your brand accent colour. This updates all four variants simultaneously. For the button dots, they use rgba(255,255,255,0.8) — white on the coloured button background.
  5. 5
    Change the dot size and gapUpdate the width and height on .dot for each variant. Change gap on .loader to control spacing between dots. For the wave variant, adjust the height on .wave .dot (default 32px) to control bar height.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component where the button loading state uses useState, or "Tailwind" for a Tailwind CSS version.

Real-world uses

Common Use Cases

Form submission and async operation feedback
The button loading state prevents double-submission and communicates that the form is being processed. Wire to fetch() or axios — add .loading on click, remove in .then()/.finally(). The cursor: not-allowed prevents impatient re-clicks.
Chat typing indicators and real-time presence
The pulse dots (3-dot variant) is the universal "someone is typing" indicator in chat UIs. Position it absolutely in the chat thread below the last message when the other user is composing a reply.
Full-page loading overlays for route transitions
Place any variant inside a fixed overlay (position:fixed, inset:0, background:rgba(255,255,255,0.9)) for a full-page loading overlay. Show on route change start, remove on completion. The bounce and spinner variants work best at large sizes for overlay contexts.
Inline content-loading placeholders
Use the pulse dots inline inside a card or widget while its data loads. Replace the dots with the actual content once the fetch resolves — same container, same position. Simpler than a full skeleton loader for single-value displays.
Study CSS keyframe animation staggering technique
The staggered animation-delay pattern — giving nth-child elements increasing delay values — is the core technique behind most multi-element CSS animations. This snippet shows three variants of the same pattern: translateY stagger, scale stagger, and scaleY stagger.
Music players and audio equaliser visualisations
The wave variant bars mimic an audio equaliser — for a real signal-driven version, see the audio waveform visualizer. Change the background to your audio player accent colour, remove the label, and position it in the "now playing" indicator area. Add animation-play-state: paused when audio is paused to freeze the bars.
Related: SVG Logo Draw-In Loader
See the SVG Logo Draw-In Loader for a related loaders pattern worth pairing with this one.
Related: Streaming Text Skeleton Reveal
See the Streaming Text Skeleton Reveal for a related loaders pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

CSS properties that trigger layout recalculation (width, height, top, left, margin, padding) cause the browser to recalculate the positions of all affected elements on every animation frame — expensive on any device. Properties handled by the GPU compositor (transform: translateY, scale, rotate and opacity) bypass layout and paint entirely. The GPU moves pixels directly. This is why these dot animations run at 60fps even on mobile — they never touch the main thread.

The .loading class sets cursor: not-allowed and the triggerLoad() function returns early if the button already has the .loading class (if (btn.classList.contains("loading")) return). For a more robust solution, also set btn.disabled = true on click and btn.disabled = false in your .finally() handler — the disabled attribute prevents click events at the browser level, not just visually.

Click "JSX" to download. In React: const [loading, setLoading] = useState(false). On click: setLoading(true); try { await yourApiCall(); setSuccess(true); } finally { setLoading(false); }. Conditionally render the text or dots: {loading ? <Dots /> : "Save changes"}. Add disabled={loading} to the button element. Use useRef if you need to reset button text after success.

Use IntersectionObserver: const obs = new IntersectionObserver(([entry]) => { entry.target.style.animationPlayState = entry.isIntersecting ? "running" : "paused"; }); obs.observe(document.querySelector(".wave")). This pauses the animation when the element is scrolled out of view, saving GPU resources on long pages with multiple loaders.