Loaders✨ Premium

Orbit

A 12 px satellite orbits a fixed 8 px center dot in exactly 1 second — 19 px radius measured, two pseudo-elements, no JavaScript.

CSSCircular

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Loaders has 29 effects, including 4 free. Explore the category →

3 usage examples

VPN app waiting screen: connecting to the server — Orbit example 1

① VPN app waiting screen: connecting to the server

WhenThe main screen of a VPN app (or an online game, or a connected device) between picking a server and the tunnel coming up: the client looks for its server, exchanges keys, brings up the tunnel — 2 to 6 seconds during which the main area has nothing else to show.
WhyHere the loader is the only content on screen, at 60 px in the center: it has to tell the situation, not just fill the wait. Two distinct bodies do exactly that — the fixed center is the server, the moving satellite is the client still looking for it, not yet docked. A ring or an indeterminate bar says "please wait"; the orbit says "two parties, not yet linked", which is precisely the state being shown. A dashed 46 px track drawn around it (scene decoration) makes the orbit explicit from the first frame.
SettingsContainer width: 60px; height: 60px; ::before at 14px, transform-origin: 50% 30px (radius: 30 − 7 = 23 px); ::after at 10px; default colors (#6366f1 / #d946ef) and default 1 s duration; optional track laid by the scene under the loader: 46 × 46 px, border: 1px dashed rgba(255,255,255,.14), border-radius: 50%, centered.
Video player buffering overlay — Orbit example 2

② Video player buffering overlay

WhenA full-frame video player: playback stalls on a frozen frame, and the loader appears centered as an overlay while the buffer fills.
WhyThe circular orbit — distinct from the classic CSS wheel (fx-0421) — evokes a satellite "in orbit", coherent with streaming interfaces that borrow space imagery. The indigo + magenta pair holds up on any dark video poster.
SettingsDefault size 50 × 50 px; ::before { background: #fff; animation-duration: 1.2s } for a slightly calmer orbit; ::after { background: rgba(255,255,255,.6) } for a subtle center; container at position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%) over the player wrapper.
GPS signal acquisition in a hiking app — Orbit example 3

③ GPS signal acquisition in a hiking app

WhenThe start-activity screen of a GPS tracking app (hiking, running, fleet): the map is there, but the position dot does not exist yet — the receiver is waiting for a 4-satellite fix, 10 to 30 seconds under open sky, longer in forests or cities.
WhyThe orbit literally depicts what the user is waiting for: a satellite passing over a receiver. The fixed center holds the place of the position dot to come, the moving satellite shows the signal being acquired. No other loader carries this reading — a wheel or bouncing dots would only say "loading", whereas here the shape explains the delay, and the slow 2 s orbit matches a wait of several tens of seconds.
SettingsDefault geometry (50 × 50 px container, 12 px satellite, transform-origin: 50% 25px, 19 px radius); ::before { background: #fff; animation-duration: 2s }; hollow-ring center: ::after { width: 12px; height: 12px; background: none; border: 2px solid #a5b4fc } — its size has no effect on the path; the loader sits centered at the top of a translucent panel laid over the map.

How it works

The .loader-orbit container is a 50 × 50 px square with position: relative. Two pseudo-elements, no extra DOM. ::after: an 8 × 8 px disc, #d946ef, centered by left: 50%; top: 50%; transform: translate(-50%, -50%) — it never moves. ::before: a 12 × 12 px disc, #6366f1, anchored at left: 50%; top: 0, with transform-origin: 50% 25px.

The value transform-origin: 50% 25px places the pivot at (6 px, 25 px) in the ::before's local coordinate system: 6 px is half of 12 px (horizontal center of the disc), 25 px is half of 50 px (exact vertical midpoint of the container). The single keyframe to { transform: translateX(-50%) rotate(360deg) } combines two simultaneous effects: translateX(-50%) shifts the orbit center 6 px to the left, bringing the pivot from (31 px, 25 px) to (25 px, 25 px) — the container's geometric center; rotate(360deg) then spins the disc around that point. Resulting radius: 25 − 6 = 19 px, measured constant across 8 successive 125 ms samples.

The 1s linear infinite animation produces a constant 360 °/s, clockwise. No JavaScript, no CSS variables, no element created at load time. The implicit from keyframe value is the transform: translateX(-50%) declared in the element's CSS, which ensures a seamless loop between the starting position (disc at the top, centered) and the continuous orbit.

Accessibility

  • prefers-reduced-motion not handled: the animation runs continuously regardless of the system preference. Measured under reducedMotion: reduce in Playwright: the sine component of the transform goes from 0.5430 to −0.5450 over 500 ms — the orbit does not slow down. Add @media (prefers-reduced-motion: reduce) { .loader-orbit::before { animation: none } } to stop the rotation and leave the satellite frozen at its starting position.
  • For a loading indicator, no semantics are provided: .loader-orbit is an empty div with no role, no aria-busy, no aria-label, and no aria-live. Screen readers ignore both pseudo-elements (CSS-generated content). Add at minimum role="status" and aria-label="Loading"; if this loader replaces dynamic content, wrap it in an aria-live="polite" region.
  • The effect works fully on mobile (CSS Animations require no touch interaction): the satellite orbits from page load with no prior gesture. No custom cursor, no mouse events — drop-in compatible with touch contexts.
  • Contrast: both discs are UI state indicators, not text (WCAG 3:1 threshold for UI components). #6366f1 on #0a0a0f: 4.4:1 ✓. #d946ef on #0a0a0f: 5.3:1 ✓. On a light background, both colors fall below 3:1 — replace #6366f1 with #4338ca and #d946ef with #a21caf to meet the threshold on white.

Browser compatibility

Pure CSS: @keyframes, transform with translateX() and rotate(), transform-origin, border-radius: 50%, ::before/::after pseudo-elements. No dependencies.

Chrome 36+✓ Full
Firefox 16+✓ Full
Safari 9+✓ Full
Edge 12+✓ Full
Mobile iOS 9+✓ Full
Android Chrome 36+✓ Full

Without CSS Animations (IE 9 and older), ::before renders at its initial state — a #6366f1 disc frozen at the top of the container, horizontally centered. ::after stays centered. No JavaScript to load, no console errors.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="loader-orbit"></div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Container size (CSS — .loader-orbit { width: 50px; height: 50px }) 50 × 50 px Rescales the whole system. After a change, recalculate the satellite's transform-origin: its Y value must remain equal to half the new height for the pivot to stay centered. The two disc sizes are independent and adjust separately.
Orbit radius (CSS — ::before { transform-origin: 50% 25px }) 25 px arm → 19 px effective radius The Y value of transform-origin is the rotation arm; effective radius = arm − half satellite height (25 − 6 = 19 px). Lower to 20px for a 14 px radius (tight orbit); raise to 30px for 24 px (resize the container to ≥ 62 × 62 px accordingly).
Satellite size (CSS — ::before { width: 12px; height: 12px }) 12 × 12 px Also adjust transform-origin X: 50% of the new width (e.g. 8pxtransform-origin: 4px 25px). Without this adjustment, the satellite orbits slightly off-center.
Satellite color (CSS — ::before { background: #6366f1 }) #6366f1 (indigo) Independent from the center. Any CSS color value works: gradient (background: linear-gradient(...)), semi-transparent, white. No border or box-shadow in the sold code — add them if a glow is needed.
Center color (CSS — ::after { background: #d946ef }) #d946ef (magenta) Can also be transparent to remove the center dot and leave only the orbiting satellite, or background: none; border: 2px solid #d946ef; border-radius: 50% for a hollow ring.
Duration (CSS — animation: 1s linear) 1 s — 360 °/s 0.5s: fast orbit, signals intense activity. 2s: slow orbit, suited to long waits or editorial contexts. The linear value keeps angular velocity constant; ease-in-out would produce a perceptible acceleration and deceleration at every pass through the top.
Center size (CSS — ::after { width: 8px; height: 8px }) 8 × 8 px No effect on the satellite's path. A center of 0 px (display: none) gives a lone comet effect. A center of 20 px (40% of the container) reinforces the planet/moon metaphor.

FAQ

Three values are coupled: the container height, the Y value of transform-origin, and half the satellite height. The formula is: radius = transform-origin-Y − (satellite-height / 2). For a 60 × 60 px container with a 14 px satellite, set transform-origin: 7px 30px (30 = 60/2, 7 = 14/2): radius = 30 − 7 = 23 px. Never change one value without recalculating the others, or the pivot will drift from the center.
Classic Spinner (fx-0421) is a ring with a variable-width border that rotates on itself — one body, "wheel" metaphor. Gradient Ring (fx-0435) is a conic-gradient ring that rotates — one body, "arc" metaphor. Orbit provides two distinct bodies: a fixed center and a moving satellite — planetary metaphor, legible even at 24 px, with no border thickness or gradient. Choose Orbit when the design calls for a system rather than a circle.
Yes, by adding a second HTML element (or nesting a child inside .loader-orbit) with a different transform-origin and duration. The sold code contains only one satellite (the ::before); adding a second child div with position: absolute and its own rotation animation is the cleanest path — no changes to the existing code.