Components
Animated Beam
A smooth, animated beam component that connects elements with customizable curves and 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?