webdevpuneet.com

Orbit Loader — Animated Orbiting Dots Spinner

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

CategoryLoaders

Copy & paste

Source Code

<div class="ob-stage">
  <div class="ob-loader" id="obLoader" role="status" aria-label="Loading">
    <div class="ob-core"></div>
    <div class="ob-orbit ob-o1"><span class="ob-planet"></span></div>
    <div class="ob-orbit ob-o2"><span class="ob-planet"></span></div>
    <div class="ob-orbit ob-o3"><span class="ob-planet"></span></div>
  </div>
  <div class="ob-controls">
    <button type="button" class="ob-chip is-on" data-speed="1">1x</button>
    <button type="button" class="ob-chip" data-speed="1.8">Fast</button>
    <button type="button" class="ob-chip" data-speed="0.5">Slow</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), 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

Orbiting planets
Dots ride rotating rings — no per-frame math.
Concentric orbits
Three nested rings at different speeds and directions.
Pulsing core
A glowing center scales and fades on its own loop.
CSS-variable speed
One --spd variable re-times every animation proportionally.
Status role
role=status + aria-label announce loading.
Reduced-motion safe
Animation disables for prefers-reduced-motion.
Tiny and crisp
Built from divs — no images or SVG.
No library
Pure HTML/CSS/JS — no spinner dependency.

About this UI Snippet

Orbit Loader — Pure-CSS Orbiting Spinner with Speed Control

Screenshot of the Orbit Loader snippet rendered live

An orbit loader is a loading spinner styled as planets circling a glowing core — a more characterful alternative to a plain ring while staying lightweight and CSS-only. This snippet builds one with three independent orbits, a pulsing core, a speed control via a CSS variable, and reduced-motion support, in plain HTML and CSS with a sprinkle of JavaScript.

Orbits as rotating rings

Each orbit is a circular element that rotates with a CSS @keyframes spin, and a small "planet" dot sits at the top of each ring. Because the planet is a child of the rotating ring, spinning the ring carries the planet around its circular path — no per-frame trigonometry. Nesting three rings at decreasing inset gives concentric orbits, and giving each a different duration (and alternating direction) makes the motion feel organic rather than mechanical.

A pulsing core

In the centre, a radial-gradient core gently scales and fades on its own loop with a glowing box-shadow, anchoring the composition and adding life. The whole loader is built from a handful of divs with no images or SVG, so it's tiny and crisp at any size.

Speed via a CSS variable

All animation durations are expressed as a base time divided by a --spd custom property (e.g. calc(1.6s / var(--spd))). Changing one variable on the loader re-times every orbit and the pulse together, proportionally — so the speed control is a single style.setProperty('--spd', …) call, and a designer can re-tune the whole loader by editing one number. This is a clean demonstration of CSS variables driving coordinated animation.

Accessible and motion-safe

The loader is marked role="status" with an aria-label, so assistive tech announces that content is loading rather than ignoring a decorative spinner. A prefers-reduced-motion media query stops the animation for users who opt out, leaving a static, still-meaningful graphic — the responsible default for any looping animation.

Drop-in and themeable

Colours, sizes, orbit count, and speeds are all CSS, so restyling to match a brand or simplifying to two orbits takes minutes. It's a polished, dependency-free reference for a distinctive loading state that's still feather-light.

Step by step

How to Use

  1. 1
    Paste HTML and CSSAn orbit loader renders with three orbiting dots and a pulsing core.
  2. 2
    Drop it inShow it while content or a request is loading.
  3. 3
    Set the speedUse the chips, or set the --spd variable from your code.
  4. 4
    RecolorChange each planet and the core gradient to match your brand.
  5. 5
    Adjust orbitsAdd or remove an orbit ring and tune its duration.
  6. 6
    Respect motionIt already stops under prefers-reduced-motion.

Real-world uses

Common Use Cases

Page and route loading
Replace a plain ring with a characterful spinner over a loading overlay, using three nested orbits at different speeds.
Async button states
Pair with a loading button for longer operations, where a pulsing core draws attention while work continues.
Boot and splash screens
Give a first paint some personality, with planets riding rotating rings so no per-frame JavaScript is needed.
Empty-state background work
Show background activity next to an empty state, while a single --spd variable retimes every animation proportionally.
Nested rotation reference
Study how nested rotating wrappers create orbits cheaply, and how reduced-motion support turns the spin off for users who prefer it.
Related: Skeleton Chat Message Loader
See the Skeleton Chat Message Loader for a related loaders pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Each orbit is a ring element that rotates via a CSS keyframe animation, and the planet dot is a child positioned at the top of that ring. When the ring rotates, the child rotates with it around the center, tracing a circle — so the orbit is pure CSS transform, with no requestAnimationFrame or trigonometry. Nesting rings at smaller insets creates concentric orbits.

Every animation duration is written as a base time divided by a --spd CSS variable, like calc(1.6s / var(--spd)). Setting --spd on the loader (the chips call style.setProperty) re-times all orbits and the core pulse together and proportionally, so the whole loader speeds up or slows down in sync from a single value. It is a neat example of variables coordinating multiple animations.

The loader has role="status" and an aria-label, so screen readers announce that something is loading instead of treating it as a meaningless decoration. It also respects prefers-reduced-motion, disabling the animation for users who are sensitive to motion while leaving a static graphic, which is the responsible way to ship any looping animation.

Yes. Add or remove an .ob-orbit element (with its planet) and give it an inset and animation duration; everything is CSS. Colors are set per planet and on the core's radial gradient, so recoloring to a brand palette or simplifying to two orbits takes a few lines and no logic changes.

Render the markup as a small component and toggle it while a loading flag is true. Expose speed as a prop that sets the --spd CSS variable via an inline style. The animations are pure CSS, so they port directly, and prefers-reduced-motion still applies. Tailwind users can apply sizing and colors with utilities and keep the keyframes in the stylesheet.