Orbit Loader — Animated Orbiting Dots Spinner

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

Share & Support

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.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't need to trace every calc() by hand to see how the timing holds together. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain precisely how a single --spd custom property, referenced inside calc(1.6s / var(--spd)) on three different orbit rings and the core pulse, keeps every animation proportionally in sync when the speed chips call setProperty, or why the ob-o2 ring adds the reverse keyword while the others don't. The same assistant can help optimize it — ask whether running three concurrent infinite CSS animations is meaningfully cheaper than a single requestAnimationFrame driving all three via transform, especially on lower-powered devices. It's also useful for extending the loader: have it add a fourth orbit with its own inset and duration, wire the core's pulse color to reflect a loading-progress percentage, or expose the speed chips as a prop-driven component instead of hardcoded buttons. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build an "orbit loader" spinner in plain HTML, CSS, and a small amount of JavaScript using only CSS keyframe animations and a custom property for speed control — no requestAnimationFrame, no JS-driven positioning math.

Requirements:
- A central pulsing "core" element using a radial-gradient background and a glowing box-shadow, animated with a CSS keyframe that scales it up and fades its opacity partway through the loop and back, set to loop infinitely.
- Three concentric ring elements nested at different inset values (so they form circles of decreasing radius), each with a small colored "planet" dot positioned at the top of the ring via absolute positioning, so that rotating the ring carries its planet around a circular path with zero per-frame JavaScript math.
- Give each ring a different animation duration and have at least one ring rotate in the reverse direction from the others, so the composition doesn't feel mechanically uniform.
- Express every animation's duration as a base time divided by a single shared CSS custom property (e.g. calc(1.6s / var(--spd))) defined once on the loader's root element, so changing that one custom property re-times the core pulse and all three orbit rings together, proportionally.
- Add a small row of speed-selector buttons (e.g. 1x, Fast, Slow) that, on click, mark themselves active and call element.style.setProperty to update the shared speed custom property on the loader.
- Mark the loader element with role="status" and an aria-label describing that content is loading, and add a prefers-reduced-motion media query that disables all the animations for users who have that preference set.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

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
A branded spinner over a loading overlay.
Button loading states
Pair with a loading button for async actions.
Splash and boot screens
A lively loader on first paint.
Data-fetch placeholders
Show while a chart or list resolves.
Empty states
Indicate background work near an empty state.
Learning CSS animation
A reference for nested rotations and variable timing.

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.