Components

Animated Beam

A smooth, animated beam component that connects elements with customizable curves and effects.

Last updated on

Edit on GitHub

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 motion

Copy 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 containerRef must be attached to the parent container that holds both the start and end elements
  • Use BeamContainer as the wrapper for proper positioning
  • Use BeamNode for 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?

On this page