Orbiting Circles — <mv-orbit>
Items orbiting a center: multiple rings (radius, duration, direction), even spacing, icons that stay upright, drawn paths; 100% CSS animation, paused when off-screen.
| Category | Layout |
|---|---|
| Type | Web Component (<mv-orbit>) |
| Status | stable |
| Keywords | orbit, circles, integrations, rotation, hero, decorative |
When to use
- A hero or integrations section shows partner icons revolving around a central logo
- A decorative constellation with several rings of different speeds and directions is wanted
Avoid when
- Actual connections between services must be drawn as linked paths → use Animated Beam instead
- Users need to click or read the satellites; moving targets are hard to hit and scan
- Meaning relies only on the motion; it is decorative and freezes under reduced motion
Install
node scripts/add.mjs orbiting-circles --out ./src/marvelousAI agent with the Marvelous UI MCP server: install_components({ slugs: ["orbiting-circles"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, core/element.js, core/observe.js, components/orbiting-circles/orbiting-circles.js, components/orbiting-circles/orbiting-circles.css.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<div id="orbit-demo" style="display:grid;place-items:center;width:100%">
<style>
#orbit-demo .ob-core { display: grid; place-items: center; width: 4.5rem; height: 4.5rem; border-radius: 1.4rem; color: #fff;
background: linear-gradient(145deg, oklch(0.72 0.17 var(--mv-accent-h)), oklch(0.48 0.24 calc(var(--mv-accent-h) + 20)));
box-shadow: 0 0 0 8px color-mix(in oklch, var(--mv-accent) 12%, transparent), 0 20px 50px -10px color-mix(in oklch, var(--mv-accent) 60%, transparent); }
#orbit-demo .ob-core svg { width: 2.2rem; height: 2.2rem; }
#orbit-demo .ob-node { border-radius: 50%;
border: 1px solid var(--mv-border); background: var(--mv-surface-raised); color: var(--mv-fg); box-shadow: var(--mv-shadow-md); }
#orbit-demo .ob-node svg { width: 48%; height: 48%; }
#orbit-demo .ob-node[style*="--hue"] { border: 0; color: #fff; background: linear-gradient(145deg, oklch(0.74 0.15 var(--hue)), oklch(0.52 0.2 calc(var(--hue) + 30))); }
#orbit-demo .ob-av { border-radius: 50%; font-size: .75rem; font-weight: 700; color: #fff;
border: 2px solid var(--mv-bg); box-shadow: var(--mv-shadow-md); background: linear-gradient(135deg, oklch(0.75 0.14 var(--hue)), oklch(0.5 0.19 calc(var(--hue) + 40))); }
</style>
<mv-orbit path aria-label="Integrations ecosystem" role="img">
<div data-center><span class="ob-core"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 18V7l7 6 7-6v11"/></svg></span></div>
<div class="mv-orbit-ring" data-radius="78" data-duration="14" style="--mv-orbit-item-size: 2.5rem">
<span class="ob-node" style="--hue:250"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg></span>
<span class="ob-node" style="--hue:150"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a8 8 0 0 1-11.6 7.1L4 20l1-4.6A8 8 0 1 1 21 12z"/></svg></span>
<span class="ob-node" style="--hue:25"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg></span>
</div>
<div class="mv-orbit-ring" data-radius="140" data-duration="26" data-reverse data-path="dashed">
<span class="ob-node"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></span>
<span class="ob-node"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.7 3.6 3 8 3s8-1.3 8-3V5M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg></span>
<span class="ob-node"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 10h-1.3A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"/></svg></span>
<span class="ob-node"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="m7 15 4-4 3 3 5-6"/></svg></span>
<span class="ob-node"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>
</div>
<div class="mv-orbit-ring" data-radius="200" data-duration="44" style="--mv-orbit-item-size: 2.4rem; --mv-orbit-offset: 20deg">
<span class="ob-av" style="--hue:280">PS</span>
<span class="ob-av" style="--hue:200">KT</span>
<span class="ob-av" style="--hue:330">AO</span>
<span class="ob-av" style="--hue:30">LB</span>
<span class="ob-av" style="--hue:150">MR</span>
<span class="ob-av" style="--hue:90">TN</span>
</div>
</mv-orbit>
</div>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
radius | number | 120 | Radius (px) of the default ring (children outside any .mv-orbit-ring). |
duration | number | 20 | Duration of one revolution (s) of the default ring. |
reverse | boolean | Reverse direction for the default ring. | |
path | boolean | Draws each orbit’s circle. | |
paused | boolean | Freezes the orbits. |
Content structure
| Name | Description |
|---|---|
[data-center] | Centered element (logo, avatar…). |
.mv-orbit-ring | A ring: data-radius (px), data-duration (s), data-reverse, data-path="dashed|none". Its children orbit. |
other children | Grouped into a default ring driven by the element’s attributes. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-orbit-size | 2 × max radius + 64px | Size of the square container. |
--mv-orbit-item-size | 2.75rem | Item size (can be set per ring). |
--mv-orbit-offset | 0deg | Starting angular offset of a ring. |
--mv-orbit-radius | Radius in any CSS unit (takes precedence over data-radius). | |
--mv-orbit-path-color / --mv-orbit-path-style | Color and style of the orbit stroke. |
Accessibility
Purely visual: give the element role="img" + aria-label (or aria-hidden) when the satellites are decorative. Reduced motion: the constellation stays frozen at its starting angles. Pauses automatically when off-screen.