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.

CategoryEffects
TypeWeb Component (<mv-animated-beam>)
Statusstable
Keywordsbeam, svg, connection, integrations, diagram, flow

When to use

Avoid when

Install

node scripts/add.mjs animated-beam --out ./src/marvelous

AI 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

NameTypeDefaultDescription
from / toCSS selectorConnected elements (looked up in the parent, then the document).
curvaturenumber0Curvature (px) perpendicular to the chord; positive = bulges upward for a left → right beam.
durationnumber3Duration of one pulse pass (s).
delaynumber0Initial delay (s), to stagger several beams.
reversebooleanThe pulse travels from to toward from.
start-x / start-y / end-x / end-ynumber0Endpoint offsets (px) from the centers.

Methods

NameDescription
refresh()Recomputes the path (after an endpoint moves without a resize).

Content structure

NameDescription
positioned parentThe element covers its parent (position: relative required); place the nodes above it (z-index).

CSS variables

NameDefaultDescription
--mv-beam-from / --mv-beam-toaccent → shifted huePulse colors (start → end).
--mv-beam-width2pxPulse thickness.
--mv-beam-path-color / --mv-beam-path-widthColor 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.