Animated Beam
The Animated Beam draws a path between two elements and sends a gradient comet along it. Point it at any two nodes with template refs and it measures their positions itself, so the beam survives resizes, theme changes and layout shifts.
Give it a curvature and the path bows along the perpendicular of the line between the two refs, which means a vertical beam curves sideways and a diagonal one curves diagonally.
Usage
<template>
<div
ref="container"
class="relative flex h-64 w-full items-center justify-between rounded-xl border border-default bg-default px-10"
>
<div
ref="from"
class="z-10 flex size-12 items-center justify-center rounded-full border border-default bg-elevated"
>
<UIcon
name="i-lucide-user"
class="size-5 text-highlighted"
/>
</div>
<div
ref="to"
class="z-10 flex size-12 items-center justify-center rounded-full border border-default bg-elevated"
>
<UIcon
name="i-lucide-database"
class="size-5 text-highlighted"
/>
</div>
<MAnimatedBeam
:container-ref="container"
:from-ref="from"
:to-ref="to"
:curvature="-40"
/>
</div>
</template>
<script setup lang="ts">
const container = ref<HTMLElement | null>(null)
const from = ref<HTMLElement | null>(null)
const to = ref<HTMLElement | null>(null)
</script>
Examples
Reverse sweep
The beam always measures the elements you hand it, so there is nothing to configure in the playground — the three refs are the configuration. Here the same beam runs bottom to top through a wider curve.
<template>
<div
ref="container"
class="relative flex h-72 w-full flex-col items-center justify-between rounded-xl border border-default bg-default py-8"
>
<div
ref="from"
class="z-10 flex size-12 items-center justify-center rounded-full border border-default bg-elevated"
>
<UIcon
name="i-lucide-cloud"
class="size-5 text-highlighted"
/>
</div>
<div
ref="to"
class="z-10 flex size-12 items-center justify-center rounded-full border border-default bg-elevated"
>
<UIcon
name="i-lucide-server"
class="size-5 text-highlighted"
/>
</div>
<MAnimatedBeam
:container-ref="container"
:from-ref="from"
:to-ref="to"
:curvature="70"
:duration="3"
reverse
/>
</div>
</template>
<script setup lang="ts">
const container = ref<HTMLElement | null>(null)
const from = ref<HTMLElement | null>(null)
const to = ref<HTMLElement | null>(null)
</script>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
containerRef | HTMLElement | null | null | The positioned element the beam is drawn inside. The <svg> overlay is sized to it. Required: the beam renders nothing without it. |
fromRef | HTMLElement | null | null | The element the beam leaves from. Its centre is the start of the path. Required. |
toRef | HTMLElement | null | null | The element the beam arrives at. Its centre is the end of the path. Required. |
curvature | number | 0 | How far the path bows away from the straight line, in pixels. Negative values bow the other way. |
duration | number | 4 | Seconds for one sweep. |
delay | number | 0 | Seconds to wait before the first sweep. |
size | number | 72 | Arc length of the travelling comet, in pixels. It is clamped so the comet can never fill the whole path. |
reverse | boolean | false | Sweep from toRef back towards fromRef. |
pathWidth | number | 2 | Thickness of both the static path and the comet, in pixels. |
pathColor | string | 'var(--ui-border)' | Colour of the static path the comet rides along. |
pathOpacity | number | 1 | Opacity of the static path. |
gradientStartColor | string | 'var(--ui-primary)' | Colour the comet's tail fades from, at zero opacity. |
gradientStopColor | string | 'var(--ui-secondary)' | Colour the comet's head fades up to. |
startXOffset | number | 0 | Nudge the start point along the x axis. |
startYOffset | number | 0 | Nudge the start point along the y axis. |
endXOffset | number | 0 | Nudge the end point along the x axis. |
endYOffset | number | 0 | Nudge the end point along the y axis. |
class | string | '' | CSS classes to apply to the <svg> overlay. |
UI
This component's theme exposes the slots below. Override any of them per instance with the ui prop, or app-wide under ui.animatedBeam in app.config.ts.
| Prop | Type | Default | Description |
|---|---|---|---|
ui | object | - | Per-slot classes merged over the theme defaults. |
| Slot | Props | Description |
|---|---|---|
svg | - | pointer-events-none absolute inset-0 h-full w-full overflow-visible |