A compact loader with two counter-rotating orbit rings carrying satellites around a softly breathing accent core, implemented with CSS keyframes.
Preview
Customize
4 props72
1.4
Installation
Copies the component source and its CSS Module into your project.
npx motionfoundry add orbit-loaderWrites components/ui/orbit-loader.tsx and components/styles/orbit-loader.module.css.
Usage
Updates live as you change the controls — only props that differ from the defaults are included.
example.tsx
1import { OrbitLoader } from "@motionfoundry/ui";2 3export function Example() {4 return (5 <OrbitLoader />6 );7}Props
Accessibility
Root uses role="status" with an aria-label (default 'Loading'); all visual parts are aria-hidden. Under prefers-reduced-motion rotation and breathing stop, leaving a static orbit composition.
Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.
Source
components/ui/orbit-loader.tsx
1"use client";2 3import * as React from "react";4import styles from "../styles/orbit-loader.module.css";5 6export interface OrbitLoaderProps {7 /** Loader diameter in pixels */8 size?: number;9 /** Outer orbit period in seconds (inner orbit runs at twice this, reversed) */10 duration?: number;11 /** Accent color for the core and leading satellite */12 color?: string;13 /** Accessible label announced by assistive technology */14 label?: string;15 /** Optional className merged onto the root */16 className?: string;17}18 19export function OrbitLoader({20 size = 72,21 duration = 1.4,22 color = "#38bdf8",23 label = "Loading",24 className = "",25}: OrbitLoaderProps) {26 const styleVars = {27 "--mf-ol-size": `${size}px`,28 "--mf-ol-duration": `${duration > 0 ? duration : 1.4}s`,29 "--mf-ol-color": color,30 } as React.CSSProperties;31 32 return (33 <div role="status" aria-label={label} style={styleVars} className={`${styles.root} ${className}`.trim()}>34 <span aria-hidden="true" className={styles.core} />35 <span aria-hidden="true" className={`${styles.ring} ${styles.outer}`}>36 <span className={`${styles.dot} ${styles.accentDot}`} />37 </span>38 <span aria-hidden="true" className={`${styles.ring} ${styles.inner}`}>39 <span className={styles.dot} />40 </span>41 </div>42 );43}