Icons, labels and shapes circling a central logo on rings, each with its own speed and direction.
Customize
Usage
import { Orbit } from "@/components/motionkit/orbit";
<Orbit
center={<Logo />}
rings={[
{ radius: 62, duration: 20, items: [<MailIcon />, <SearchIcon />, <FilesIcon />] },
{ radius: 108, duration: 32, reverse: true, items: [<DeployIcon />, <MediaIcon />, <SettingsIcon />] },
]}
/>Motion
The exact curves this component runs, plotted with Motion's own maths.
Inner ring lap
Runs on its own
Outer ring lap
Runs on its own
Works in the App Router as is. Requires Tailwind CSS v4.
npm install motionSave as components/motionkit/orbit.tsx.
"use client";
import { useState, type CSSProperties, type ReactNode } from "react";
import { useReducedMotion } from "motion/react";
export type OrbitRing = {
/** Things that circle on this ring: icons, labels or little shapes. */
items: ReactNode[];
/** Distance from the centre in pixels. */
radius: number;
/** Seconds for one full lap. */
duration?: number;
/** Travel anticlockwise instead of clockwise. */
reverse?: boolean;
};
type OrbitProps = {
/** What everything circles around, e.g. a logo. */
center?: ReactNode;
rings: OrbitRing[];
/** Freeze the orbits while the pointer is over them. */
pauseOnHover?: boolean;
className?: string;
};
export function Orbit({ center, rings, pauseOnHover = true, className = "" }: OrbitProps) {
const reduceMotion = useReducedMotion();
const [paused, setPaused] = useState(false);
const size = (Math.max(...rings.map((ring) => ring.radius), 0) + 32) * 2;
const moving = !reduceMotion && !paused;
return (
<div
onPointerEnter={pauseOnHover ? () => setPaused(true) : undefined}
onPointerLeave={pauseOnHover ? () => setPaused(false) : undefined}
style={{ width: size, height: size }}
className={`relative ${className}`}
>
{/* the individual rotate property spins without disturbing each element's translate */}
<style>{"@keyframes motionkit-orbit{to{rotate:360deg}}@keyframes motionkit-orbit-reverse{to{rotate:-360deg}}"}</style>
{rings.map((ring, r) => (
<Ring key={r} ring={ring} moving={moving} />
))}
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">{center}</div>
</div>
);
}
function Ring({ ring, moving }: { ring: OrbitRing; moving: boolean }) {
const { items, radius, duration = 20, reverse = false } = ring;
// CSS animations, not Motion: they can pause mid-lap and carry on from the same spot
const spin = (backwards: boolean): CSSProperties => ({
// longhands only: React warns when the animation shorthand and play-state are mixed
animationName: backwards ? "motionkit-orbit-reverse" : "motionkit-orbit",
animationDuration: `${duration}s`,
animationTimingFunction: "linear",
animationIterationCount: "infinite",
animationPlayState: moving ? "running" : "paused",
});
return (
<>
<div
aria-hidden
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-dashed border-white/10"
style={{ width: radius * 2, height: radius * 2 }}
/>
<ul className="absolute inset-0" style={spin(reverse)}>
{items.map((item, index) => {
const angle = (index / items.length) * 360;
return (
<li
key={index}
className="absolute left-1/2 top-1/2"
style={{ transform: `rotate(${angle}deg) translateX(${radius}px) rotate(${-angle}deg)` }}
>
{/* spins the other way at the same speed, so items stay upright as they travel */}
<div className="-translate-x-1/2 -translate-y-1/2" style={spin(!reverse)}>
{item}
</div>
</li>
);
})}
</ul>
</>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
| center | ReactNode | — | What everything circles around, e.g. a logo. |
| rings | { items, radius, duration?, reverse? }[] | — | Each ring's items, distance from the centre, seconds per lap and direction. |
| pauseOnHover | boolean | true | Freeze the orbits while the pointer is over them. |
Marquee
A seamless infinite scroller for logos and testimonials that pauses on hover.
Discover
Design
Build
Motion Path
A line that draws itself through your steps when the section scrolls into view, with a dot of light travelling along it.
We build
design systems, landing pages, online stores
Typewriter
Types a phrase, pauses, backspaces and types the next, with a human rhythm and a caret that blinks only at rest.