Animated Beam
SVG-based animated beam component for React. Create stunning connection lines between elements with customizable curves, gradients, and pulse effects.
Last updated on
AI Agent Workflow
A demonstration of a complex AI agent workflow with multiple steps and feedback loops.
import { Bot, Search, User, Zap } from "lucide-react";
import React from "react";
import {
AnimatedBeam,
BeamContainer,
BeamNode,
} from "@/components/ui/animated-beam";
export function AnimatedBeamDemo() {
const containerRef = React.useRef<HTMLDivElement>(null);
const userRef = React.useRef<HTMLDivElement>(null);
const aiRef = React.useRef<HTMLDivElement>(null);
const searchRef = React.useRef<HTMLDivElement>(null);
const resultRef = React.useRef<HTMLDivElement>(null);
return (
<BeamContainer
ref={containerRef}
className="mx-auto flex w-full items-center justify-between rounded-xl border bg-background p-10 shadow-sm"
>
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={userRef}
className="h-12 w-12 border-2 border-blue-500/20 bg-blue-500/10"
>
<User className="h-6 w-6 text-blue-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
User
</span>
</div>
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={aiRef}
className="h-16 w-16 border-2 border-purple-500/20 bg-purple-500/10 shadow-[0_0_15px_rgba(168,85,247,0.2)]"
>
<Bot className="h-8 w-8 text-purple-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
AI Agent
</span>
</div>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={searchRef}
className="h-12 w-12 border-2 border-amber-500/20 bg-amber-500/10"
>
<Search className="h-5 w-5 text-amber-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Search
</span>
</div>
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={resultRef}
className="h-12 w-12 border-2 border-emerald-500/20 bg-emerald-500/10"
>
<Zap className="h-5 w-5 text-emerald-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Result
</span>
</div>
</div>
<AnimatedBeam
containerRef={containerRef}
fromRef={userRef}
toRef={aiRef}
duration={3}
curvature={0.2}
gradientStartColor="#3b82f6"
gradientStopColor="#8b5cf6"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={aiRef}
toRef={searchRef}
duration={3}
delay={0.5}
curvature={-0.3}
gradientStartColor="#8b5cf6"
gradientStopColor="#f59e0b"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={searchRef}
toRef={aiRef}
duration={3}
delay={1.5}
curvature={-0.3}
reverse
gradientStartColor="#f59e0b"
gradientStopColor="#8b5cf6"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={aiRef}
toRef={resultRef}
duration={3}
delay={2.5}
curvature={0.3}
gradientStartColor="#8b5cf6"
gradientStopColor="#10b981"
/>
</BeamContainer>
);
}Secure Cloud Sync
Demonstrating a secure data transfer from a local device to cloud storage through an encryption layer.
import { Cloud, Laptop, ShieldCheck } from "lucide-react";
import React from "react";
import {
AnimatedBeam,
BeamContainer,
BeamNode,
} from "@/components/ui/animated-beam";
export function AnimatedBeamCurvedDemo() {
const containerRef = React.useRef<HTMLDivElement>(null);
const laptopRef = React.useRef<HTMLDivElement>(null);
const shieldRef = React.useRef<HTMLDivElement>(null);
const cloudRef = React.useRef<HTMLDivElement>(null);
return (
<BeamContainer
ref={containerRef}
className="mx-auto flex w-full items-center justify-between rounded-xl border bg-background p-10 shadow-sm"
>
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={laptopRef}
className="h-12 w-12 border-2 border-blue-500/20 bg-blue-500/10"
>
<Laptop className="h-6 w-6 text-blue-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Local
</span>
</div>
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={shieldRef}
className="h-14 w-14 border-2 border-emerald-500/20 bg-emerald-500/10"
>
<ShieldCheck className="h-7 w-7 text-emerald-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Encrypt
</span>
</div>
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={cloudRef}
className="h-12 w-12 border-2 border-sky-500/20 bg-sky-500/10"
>
<Cloud className="h-6 w-6 text-sky-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Cloud
</span>
</div>
<AnimatedBeam
containerRef={containerRef}
fromRef={laptopRef}
toRef={shieldRef}
duration={3}
curvature={0.4}
gradientStartColor="#3b82f6"
gradientStopColor="#10b981"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={shieldRef}
toRef={cloudRef}
duration={3}
delay={0.5}
curvature={-0.4}
gradientStartColor="#10b981"
gradientStopColor="#0ea5e9"
/>
</BeamContainer>
);
}Microservices Architecture
A complex architecture showing how an API gateway routes requests to various microservices and a shared database.
import {
Database,
Layout,
Lock,
Package,
Server,
Smartphone,
} from "lucide-react";
import React from "react";
import {
AnimatedBeam,
BeamContainer,
BeamNode,
} from "@/components/ui/animated-beam";
export function AnimatedBeamMultipleDemo() {
const containerRef = React.useRef<HTMLDivElement>(null);
const clientRef = React.useRef<HTMLDivElement>(null);
const gatewayRef = React.useRef<HTMLDivElement>(null);
const authRef = React.useRef<HTMLDivElement>(null);
const ordersRef = React.useRef<HTMLDivElement>(null);
const inventoryRef = React.useRef<HTMLDivElement>(null);
const dbRef = React.useRef<HTMLDivElement>(null);
return (
<BeamContainer
ref={containerRef}
className="mx-auto flex w-full items-center justify-center gap-12 rounded-xl border bg-background p-10 shadow-sm"
>
{/* Client Layer */}
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={clientRef}
className="h-14 w-14 border-2 border-blue-500/20 bg-blue-500/10"
>
<Smartphone className="h-6 w-6 text-blue-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Client
</span>
</div>
{/* Gateway Layer */}
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={gatewayRef}
className="h-16 w-16 border-2 border-purple-500/20 bg-purple-500/10"
>
<Server className="h-8 w-8 text-purple-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Gateway
</span>
</div>
{/* Services Layer */}
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
<BeamNode
ref={authRef}
className="h-12 w-12 border-2 border-emerald-500/20 bg-emerald-500/10"
>
<Lock className="h-5 w-5 text-emerald-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Auth
</span>
</div>
<div className="flex items-center gap-3">
<BeamNode
ref={ordersRef}
className="h-12 w-12 border-2 border-amber-500/20 bg-amber-500/10"
>
<Package className="h-5 w-5 text-amber-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Orders
</span>
</div>
<div className="flex items-center gap-3">
<BeamNode
ref={inventoryRef}
className="h-12 w-12 border-2 border-rose-500/20 bg-rose-500/10"
>
<Layout className="h-5 w-5 text-rose-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Stock
</span>
</div>
</div>
{/* Database Layer */}
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={dbRef}
className="h-14 w-14 border-2 border-slate-500/20 bg-slate-500/10"
>
<Database className="h-6 w-6 text-slate-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
DB
</span>
</div>
{/* Connections */}
<AnimatedBeam
containerRef={containerRef}
fromRef={clientRef}
toRef={gatewayRef}
duration={3}
curvature={0}
gradientStartColor="#3b82f6"
gradientStopColor="#8b5cf6"
/>
{/* Gateway to Services */}
<AnimatedBeam
containerRef={containerRef}
fromRef={gatewayRef}
toRef={authRef}
duration={3}
delay={0.2}
curvature={-0.3}
gradientStartColor="#8b5cf6"
gradientStopColor="#10b981"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={gatewayRef}
toRef={ordersRef}
duration={3}
delay={0.4}
curvature={0}
gradientStartColor="#8b5cf6"
gradientStopColor="#f59e0b"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={gatewayRef}
toRef={inventoryRef}
duration={3}
delay={0.6}
curvature={0.3}
gradientStartColor="#8b5cf6"
gradientStopColor="#f43f5e"
/>
{/* Services to DB */}
<AnimatedBeam
containerRef={containerRef}
fromRef={authRef}
toRef={dbRef}
duration={3}
delay={1}
curvature={0.3}
gradientStartColor="#10b981"
gradientStopColor="#64748b"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={ordersRef}
toRef={dbRef}
duration={3}
delay={1.2}
curvature={0}
gradientStartColor="#f59e0b"
gradientStopColor="#64748b"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={inventoryRef}
toRef={dbRef}
duration={3}
delay={1.4}
curvature={-0.3}
gradientStartColor="#f43f5e"
gradientStopColor="#64748b"
/>
</BeamContainer>
);
}Real-time Collaboration
Showing bidirectional communication between multiple users through a central relay server.
import { Server, User } from "lucide-react";
import React from "react";
import {
AnimatedBeam,
BeamContainer,
BeamNode,
} from "@/components/ui/animated-beam";
export function AnimatedBeamBidirectionalDemo() {
const containerRef = React.useRef<HTMLDivElement>(null);
const userARef = React.useRef<HTMLDivElement>(null);
const userBRef = React.useRef<HTMLDivElement>(null);
const serverRef = React.useRef<HTMLDivElement>(null);
return (
<BeamContainer
ref={containerRef}
className="mx-auto flex w-full items-center justify-between rounded-xl border bg-background p-10 shadow-sm"
>
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={userARef}
className="h-12 w-12 border-2 border-blue-500/20 bg-blue-500/10"
>
<User className="h-6 w-6 text-blue-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
User A
</span>
</div>
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={serverRef}
className="h-16 w-16 border-2 border-purple-500/20 bg-purple-500/10"
>
<Server className="h-8 w-8 text-purple-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
Relay Server
</span>
</div>
<div className="flex flex-col items-center gap-2">
<BeamNode
ref={userBRef}
className="h-12 w-12 border-2 border-amber-500/20 bg-amber-500/10"
>
<User className="h-6 w-6 text-amber-600" />
</BeamNode>
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
User B
</span>
</div>
{/* User A <-> Server */}
<AnimatedBeam
containerRef={containerRef}
fromRef={userARef}
toRef={serverRef}
duration={3}
curvature={0.3}
gradientStartColor="#3b82f6"
gradientStopColor="#8b5cf6"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={serverRef}
toRef={userARef}
duration={3}
delay={1.5}
curvature={0.3}
reverse
gradientStartColor="#8b5cf6"
gradientStopColor="#3b82f6"
/>
{/* User B <-> Server */}
<AnimatedBeam
containerRef={containerRef}
fromRef={userBRef}
toRef={serverRef}
duration={3}
curvature={-0.3}
gradientStartColor="#f59e0b"
gradientStopColor="#8b5cf6"
/>
<AnimatedBeam
containerRef={containerRef}
fromRef={serverRef}
toRef={userBRef}
duration={3}
delay={1.5}
curvature={-0.3}
reverse
gradientStartColor="#8b5cf6"
gradientStopColor="#f59e0b"
/>
</BeamContainer>
);
}Installation
CLI
npx shadcn@latest add "https://jolyui.dev/r/animated-beam"Copy and paste the following code into your project. globals.css
@layer utilities {
@keyframes beam-dash {
from {
stroke-dashoffset: 1000;
}
to {
stroke-dashoffset: -1000;
}
}
@keyframes beam-flow {
from {
transform: translateX(0%);
}
to {
transform: translateX(300%);
}
}
.animated-beam-path {
will-change: stroke-dashoffset;
}
}Manual
Install the following dependencies:
npm install motionCopy and paste the following code into your project. component/ui/animated-beam.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
interface AnimatedBeamProps {
/** Reference to the container element */
containerRef: React.RefObject<HTMLElement | null>;
/** Reference to the start element */
fromRef: React.RefObject<HTMLElement | null>;
/** Reference to the end element */
toRef: React.RefObject<HTMLElement | null>;
/** Curvature of the beam (-1 to 1, 0 is straight) */
curvature?: number;
/** Animation duration in seconds */
duration?: number;
/** Delay before animation starts */
delay?: number;
/** Reverse the animation direction */
reverse?: boolean;
/** Width of the beam path */
pathWidth?: number;
/** Color of the beam gradient start */
gradientStartColor?: string;
/** Color of the beam gradient end */
gradientStopColor?: string;
/** Starting point offset */
startXOffset?: number;
startYOffset?: number;
/** Ending point offset */
endXOffset?: number;
endYOffset?: number;
className?: string;
}
const AnimatedBeam = ({
containerRef,
fromRef,
toRef,
curvature = 0,
duration = 2,
delay = 0,
reverse = false,
pathWidth = 2,
gradientStartColor = "#18181b",
gradientStopColor = "#18181b",
startXOffset = 0,
startYOffset = 0,
endXOffset = 0,
endYOffset = 0,
className,
}: AnimatedBeamProps) => {
const [pathD, setPathD] = React.useState("");
const [svgDimensions, setSvgDimensions] = React.useState({
width: 0,
height: 0,
});
const uniqueId = React.useId();
const updatePath = React.useCallback(() => {
if (!containerRef.current || !fromRef.current || !toRef.current) return;
const containerRect = containerRef.current.getBoundingClientRect();
const fromRect = fromRef.current.getBoundingClientRect();
const toRect = toRef.current.getBoundingClientRect();
const startX =
fromRect.left - containerRect.left + fromRect.width / 2 + startXOffset;
const startY =
fromRect.top - containerRect.top + fromRect.height / 2 + startYOffset;
const endX =
toRect.left - containerRect.left + toRect.width / 2 + endXOffset;
const endY =
toRect.top - containerRect.top + toRect.height / 2 + endYOffset;
const midX = (startX + endX) / 2;
const midY = (startY + endY) / 2;
// Calculate control point for quadratic bezier curve
const dx = endX - startX;
const dy = endY - startY;
const controlX = midX - dy * curvature;
const controlY = midY + dx * curvature;
const path = `M ${startX},${startY} Q ${controlX},${controlY} ${endX},${endY}`;
setPathD(path);
setSvgDimensions({
width: containerRect.width,
height: containerRect.height,
});
}, [
containerRef,
fromRef,
toRef,
curvature,
startXOffset,
startYOffset,
endXOffset,
endYOffset,
]);
React.useEffect(() => {
updatePath();
const resizeObserver = new ResizeObserver(updatePath);
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
window.addEventListener("resize", updatePath);
return () => {
resizeObserver.disconnect();
window.removeEventListener("resize", updatePath);
};
}, [updatePath, containerRef]);
return (
<svg
className={cn(
"pointer-events-none absolute top-0 left-0 h-full w-full",
className,
)}
width={svgDimensions.width}
height={svgDimensions.height}
viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
{/* Background path gradient */}
<linearGradient
id={`beam-gradient-bg-${uniqueId}`}
gradientUnits="userSpaceOnUse"
x1="0%"
y1="0%"
x2="100%"
y2="0%"
>
<stop offset="0%" stopColor={gradientStartColor} stopOpacity="0.1" />
<stop offset="50%" stopColor={gradientStartColor} stopOpacity="0.2" />
<stop offset="100%" stopColor={gradientStopColor} stopOpacity="0.1" />
</linearGradient>
{/* Animated beam gradient */}
<linearGradient
id={`beam-gradient-${uniqueId}`}
gradientUnits="userSpaceOnUse"
x1="0%"
y1="0%"
x2="100%"
y2="0%"
>
<stop offset="0%" stopColor={gradientStartColor} stopOpacity="0" />
<stop offset="5%" stopColor={gradientStartColor} stopOpacity="1" />
<stop offset="50%" stopColor={gradientStopColor} stopOpacity="1" />
<stop offset="95%" stopColor={gradientStopColor} stopOpacity="1" />
<stop offset="100%" stopColor={gradientStopColor} stopOpacity="0" />
</linearGradient>
{/* Glow filter */}
<filter
id={`beam-glow-${uniqueId}`}
x="-50%"
y="-50%"
width="200%"
height="200%"
>
<feGaussianBlur in="SourceGraphic" stdDeviation="2" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
{/* Mask for animated beam */}
<mask id={`beam-mask-${uniqueId}`}>
<rect
className="beam-mask-rect"
x="-100%"
y="0"
width="50%"
height="100%"
fill="url(#beam-mask-gradient)"
style={{
animation: `beam-flow ${duration}s linear infinite`,
animationDelay: `${delay}s`,
animationDirection: reverse ? "reverse" : "normal",
}}
/>
</mask>
<linearGradient
id="beam-mask-gradient"
x1="0%"
y1="0%"
x2="100%"
y2="0%"
>
<stop offset="0%" stopColor="black" />
<stop offset="25%" stopColor="white" />
<stop offset="75%" stopColor="white" />
<stop offset="100%" stopColor="black" />
</linearGradient>
</defs>
{/* Background path */}
<path
d={pathD}
stroke={`url(#beam-gradient-bg-${uniqueId})`}
strokeWidth={pathWidth}
strokeLinecap="round"
fill="none"
/>
{/* Animated glowing beam */}
<path
d={pathD}
stroke={`url(#beam-gradient-${uniqueId})`}
strokeWidth={pathWidth}
strokeLinecap="round"
fill="none"
filter={`url(#beam-glow-${uniqueId})`}
className="animated-beam-path"
style={{
strokeDasharray: "20 1000",
strokeDashoffset: reverse ? "-1000" : "1000",
animation: `beam-dash ${duration}s linear infinite`,
animationDelay: `${delay}s`,
animationDirection: reverse ? "reverse" : "normal",
}}
/>
</svg>
);
};
interface BeamContainerProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
const BeamContainer = React.forwardRef<HTMLDivElement, BeamContainerProps>(
({ children, className, ...props }, ref) => {
return (
<div ref={ref} className={cn("relative", className)} {...props}>
{children}
</div>
);
},
);
BeamContainer.displayName = "BeamContainer";
interface BeamNodeProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
const BeamNode = React.forwardRef<HTMLDivElement, BeamNodeProps>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={cn(
"relative z-10 flex items-center justify-center rounded-xl border bg-background p-3 shadow-sm",
className,
)}
{...props}
>
{children}
</div>
);
},
);
BeamNode.displayName = "BeamNode";
export {
AnimatedBeam,
BeamContainer,
BeamNode,
type AnimatedBeamProps,
type BeamContainerProps,
type BeamNodeProps,
};Usage
import { AnimatedBeam, BeamContainer, BeamNode } from "@/components/ui/animated-beam";
function MyComponent() {
const containerRef = useRef<HTMLDivElement>(null);
const fromRef = useRef<HTMLDivElement>(null);
const toRef = useRef<HTMLDivElement>(null);
return (
<BeamContainer ref={containerRef}>
<BeamNode ref={fromRef}>Start</BeamNode>
<BeamNode ref={toRef}>End</BeamNode>
<AnimatedBeam
containerRef={containerRef}
fromRef={fromRef}
toRef={toRef}
/>
</BeamContainer>
);
}API Reference
AnimatedBeam
Prop
Type
BeamContainer
Prop
Type
BeamNode
Prop
Type
Notes
- The
containerRefmust be attached to the parent container that holds both the start and end elements - Use
BeamContaineras the wrapper for proper positioning - Use
BeamNodefor the elements you want to connect - Curvature values range from -1 to 1, where 0 is straight
- The animation uses SVG paths and CSS animations for smooth performance
How is this guide?
Phone Card
iPhone mockup component for React. Realistic device frame with lazy-loaded video support. Perfect for app showcases and product demos.
Expanded Map
Expandable location card with map preview for React. Click to reveal full map with 3D tilt animation. No Google Maps API required - uses OpenStreetMap tiles.