Animated Beam
ui-beats/animated-beamFreeComponent
The AnimatedBeam component draws a curved line between any two elements and sends a gradient pulse along it, remeasuring whenever the layout moves.
Install it
npx shadcn@latest add https://uibeats.com/r/animated-beam.jsonSource
1"use client";23import { motion, useReducedMotion } from "motion/react";4import { useCallback, useEffect, useId, useState, type RefObject } from "react";56interface AnimatedBeamProps {7 /** The positioned element both refs live inside. */8 containerRef: RefObject<HTMLElement | null>;9 fromRef: RefObject<HTMLElement | null>;10 toRef: RefObject<HTMLElement | null>;11 /** Arc height in pixels. Positive bows up, negative bows down, 0 is straight. */12 curvature?: number;13 /** Send the pulse from `toRef` to `fromRef` instead. */14 reverse?: boolean;15 duration?: number;16 delay?: number;17 pathColor?: string;18 gradientStart?: string;19 gradientStop?: string;20 className?: string;21}2223/**24 * Draws a curved beam between two elements and sends a gradient pulse along25 * it.26 *27 * The path is measured from the live geometry of the two elements rather than28 * hardcoded, so it survives responsive reflow, font loading and content29 * changes — a `ResizeObserver` on the container recomputes it whenever30 * anything moves.31 */32export function AnimatedBeam({33 containerRef,34 fromRef,35 toRef,36 curvature = 0,37 reverse = false,38 duration = 3,39 delay = 0,40 pathColor = "var(--border)",41 gradientStart = "var(--brand)",42 gradientStop = "var(--accent-pink)",43 className = "",44}: AnimatedBeamProps) {45 // `useId` keeps the gradient id unique — two beams on one page sharing an id46 // would both resolve to whichever was defined last.47 const gradientId = useId();48 const [path, setPath] = useState("");49 const [size, setSize] = useState({ width: 0, height: 0 });50 const prefersReducedMotion = useReducedMotion();5152 const measure = useCallback(() => {53 const container = containerRef.current;54 const from = fromRef.current;55 const to = toRef.current;56 if (!container || !from || !to) return;5758 const containerRect = container.getBoundingClientRect();59 const fromRect = from.getBoundingClientRect();60 const toRect = to.getBoundingClientRect();6162 setSize({ width: containerRect.width, height: containerRect.height });6364 // Centres, expressed relative to the container's own coordinate space.65 const startX = fromRect.left - containerRect.left + fromRect.width / 2;66 const startY = fromRect.top - containerRect.top + fromRect.height / 2;67 const endX = toRect.left - containerRect.left + toRect.width / 2;68 const endY = toRect.top - containerRect.top + toRect.height / 2;6970 const controlX = (startX + endX) / 2;71// … truncated
What it pulls in
npm packages
Files it writes
More from ui-beats
All 34 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Listanimated-list | ui-beats | Components | Free | 1 dep | |
| Border Beamborder-beam | ui-beats | Components | Free | 1 dep | |
| Bouncebounce | ui-beats | Components | Free | 1 dep | |
| Card Stackcard-stack | ui-beats | Components | Free | 1 dep | |
| Dockdock | ui-beats | Components | Free | 1 dep | |
| Fade Infade-in | ui-beats | Components | Free | 1 dep | |
| Fade In Unblurfade-in-unblur | ui-beats | Components | Free | 1 dep | |
| Flip Cardflip-card | ui-beats | Components | Free | 2 deps |
