Animated Beam — <mv-animated-beam>
Curved SVG beam between two elements of a container, traveled by a gradient pulse; follows resizes — ideal for integration diagrams.
| Category | Effects |
|---|---|
| Type | Web Component (<mv-animated-beam>) |
| Status | stable |
| Keywords | beam, svg, connection, integrations, diagram, flow |
When to use
- An integrations section must show data flowing from several tools into a central product node
- A simple architecture or pipeline diagram needs animated connections between existing elements
- Staggered or reversed pulses should suggest continuous two-way sync between services
Avoid when
- Items should circle around a central logo rather than be linked by paths → use Orbiting Circles instead
- The beam is the only thing conveying the relationship; it is decorative, so describe the link in text
Install
node scripts/add.mjs animated-beam --out ./src/marvelousAI agent with the Marvelous UI MCP server: install_components({ slugs: ["animated-beam"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/animated-beam/animated-beam.js, components/animated-beam/animated-beam.css.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<figure id="beam-demo" class="bm-hub">
<style>
#beam-demo { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 2rem;
width: 100%; max-width: 44rem; height: 23rem; padding: 1rem 2.5rem 2.5rem; margin: 0 auto;
border: 1px solid var(--mv-border); border-radius: var(--mv-radius-2xl); background: var(--mv-surface); box-shadow: var(--mv-shadow-sm); }
#beam-demo .bm-col { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 1.1rem; }
#beam-demo .bm-node { position: relative; z-index: 1; display: grid; place-items: center; width: 3rem; height: 3rem; border-radius: 50%;
border: 1px solid var(--mv-border); background: var(--mv-surface-raised); color: oklch(0.6 0.17 var(--hue, 280));
box-shadow: var(--mv-shadow-md), 0 0 18px -6px oklch(0.6 0.17 var(--hue, 280) / .5); }
#beam-demo .bm-node svg { width: 1.35rem; height: 1.35rem; }
#beam-demo .bm-core { width: 4.75rem; height: 4.75rem; border-radius: 1.4rem; color: #fff; border: 0;
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 6px color-mix(in oklch, var(--mv-accent) 14%, transparent), 0 18px 40px -12px color-mix(in oklch, var(--mv-accent) 70%, transparent); }
#beam-demo .bm-core svg { width: 2.2rem; height: 2.2rem; }
#beam-demo .bm-user { width: 3.5rem; height: 3.5rem; color: var(--mv-fg); }
#beam-demo figcaption { position: absolute; left: 50%; bottom: .9rem; translate: -50% 0; font-size: .75rem; color: var(--mv-fg-subtle); white-space: nowrap; }
@media (max-width: 560px) { #beam-demo { padding-inline: 1rem; gap: .5rem; } }
</style>
<div class="bm-col">
<span class="bm-node" id="bm-drive" style="--hue:145" title="Storage"><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="bm-node" id="bm-docs" style="--hue:250" title="Documents"><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 2v6h6M8 13h8M8 17h5"/></svg></span>
<span class="bm-node" id="bm-mail" style="--hue:25" title="Email"><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="bm-node" id="bm-cal" style="--hue:330" title="Calendar"><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>
<span class="bm-node" id="bm-chat" style="--hue:200" title="Chat"><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>
</div>
<span class="bm-node bm-core" id="bm-core" title="Marvelous"><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>
<span class="bm-node bm-user" id="bm-user" title="You"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></span>
<mv-animated-beam from="#bm-drive" to="#bm-core" curvature="-40" duration="3.2"></mv-animated-beam>
<mv-animated-beam from="#bm-docs" to="#bm-core" curvature="-18" duration="3.2" delay="0.4"></mv-animated-beam>
<mv-animated-beam from="#bm-mail" to="#bm-core" duration="3.2" delay="0.8"></mv-animated-beam>
<mv-animated-beam from="#bm-cal" to="#bm-core" curvature="18" duration="3.2" delay="1.2"></mv-animated-beam>
<mv-animated-beam from="#bm-chat" to="#bm-core" curvature="40" duration="3.2" delay="1.6"></mv-animated-beam>
<mv-animated-beam from="#bm-core" to="#bm-user" start-y="-7" end-y="-7" duration="2.6" style="--mv-beam-from: oklch(0.75 0.16 150); --mv-beam-to: oklch(0.7 0.17 200)"></mv-animated-beam>
<mv-animated-beam from="#bm-core" to="#bm-user" start-y="7" end-y="7" duration="2.6" delay="1.3" reverse style="--mv-beam-from: oklch(0.72 0.18 330); --mv-beam-to: oklch(0.72 0.17 30)"></mv-animated-beam>
<figcaption>Your tools, synced in real time</figcaption>
</figure>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
from / to | CSS selector | Connected elements (looked up in the parent, then the document). | |
curvature | number | 0 | Curvature (px) perpendicular to the chord; positive = bulges upward for a left → right beam. |
duration | number | 3 | Duration of one pulse pass (s). |
delay | number | 0 | Initial delay (s), to stagger several beams. |
reverse | boolean | The pulse travels from to toward from. | |
start-x / start-y / end-x / end-y | number | 0 | Endpoint offsets (px) from the centers. |
Methods
| Name | Description |
|---|---|
refresh() | Recomputes the path (after an endpoint moves without a resize). |
Content structure
| Name | Description |
|---|---|
positioned parent | The element covers its parent (position: relative required); place the nodes above it (z-index). |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-beam-from / --mv-beam-to | accent → shifted hue | Pulse colors (start → end). |
--mv-beam-width | 2px | Pulse thickness. |
--mv-beam-path-color / --mv-beam-path-width | Color and thickness of the static path. |
Accessibility
Decorative: aria-hidden and pointer-events: none. Describe the relationship in the content (caption, text). Reduced motion: the pulse disappears, the path stays visible and slightly stronger. The loop pauses off-screen.