Animated Beam
Animated beam connection component for React and Next.js applications. Built with TypeScript support, Tailwind CSS styling, and shadcn/ui design featuring SVG path animations, bidirectional flow, and customizable gradients.
Powered by
"use client";import React, { forwardRef, useRef } from "react";import { cn } from "@/lib/utils";import { AnimatedBeam } from "@/components/ui/shadcn-io/animated-beam";const Circle = forwardRef< HTMLDivElement, { className?: string; children?: React.ReactNode }>(({ className, children }, ref) => { return ( <div ref={ref} className={cn( "z-10 flex size-12 items-center justify-center rounded-full border-2 bg-white p-3 shadow-[0_0_20px_-12px_rgba(0,0,0,0.8)]", className, )} > {children} </div> );});Circle.displayName = "Circle";const Example = () => { const containerRef = useRef<HTMLDivElement>(null); const div1Ref = useRef<HTMLDivElement>(null); const div2Ref = useRef<HTMLDivElement>(null); return ( <div className="relative flex w-full max-w-[500px] items-center justify-center overflow-hidden p-10" ref={containerRef} > <div className="flex size-full flex-col items-stretch justify-between gap-10"> <div className="flex flex-row justify-between"> <Circle ref={div1Ref}> <Icons.user /> </Circle> <Circle ref={div2Ref}> <Icons.openai /> </Circle> </div> </div> <AnimatedBeam duration={3} containerRef={containerRef} fromRef={div1Ref} toRef={div2Ref} /> </div> );};const Icons = { openai: () => ( <svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" /> </svg> ), user: () => ( <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" strokeWidth="2" xmlns="http://www.w3.org/2000/svg" > <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /> <circle cx="12" cy="7" r="4" /> </svg> ),};export default Example;
"use client";import { motion } from "motion/react";import { RefObject, useEffect, useId, useState } from "react";import { cn } from "@/lib/utils";export interface AnimatedBeamProps { className?: string; containerRef: RefObject<HTMLElement | null>; // Container ref fromRef: RefObject<HTMLElement | null>; toRef: RefObject<HTMLElement | null>; curvature?: number; reverse?: boolean; pathColor?: string; pathWidth?: number; pathOpacity?: number; gradientStartColor?: string; gradientStopColor?: string; delay?: number; duration?: number; startXOffset?: number; startYOffset?: number; endXOffset?: number; endYOffset?: number;}export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({ className, containerRef, fromRef, toRef, curvature = 0, reverse = false, // Include the reverse prop duration = Math.random() * 3 + 4, delay = 0, pathColor = "gray", pathWidth = 2, pathOpacity = 0.2, gradientStartColor = "#ffaa40", gradientStopColor = "#9c40ff", startXOffset = 0, startYOffset = 0, endXOffset = 0, endYOffset = 0,}) => { const id = useId(); const [pathD, setPathD] = useState(""); const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 }); // Calculate the gradient coordinates based on the reverse prop const gradientCoordinates = reverse ? { x1: ["90%", "-10%"], x2: ["100%", "0%"], y1: ["0%", "0%"], y2: ["0%", "0%"], } : { x1: ["10%", "110%"], x2: ["0%", "100%"], y1: ["0%", "0%"], y2: ["0%", "0%"], }; useEffect(() => { const updatePath = () => { if (containerRef.current && fromRef.current && toRef.current) { const containerRect = containerRef.current.getBoundingClientRect(); const rectA = fromRef.current.getBoundingClientRect(); const rectB = toRef.current.getBoundingClientRect(); const svgWidth = containerRect.width; const svgHeight = containerRect.height; setSvgDimensions({ width: svgWidth, height: svgHeight }); const startX = rectA.left - containerRect.left + rectA.width / 2 + startXOffset; const startY = rectA.top - containerRect.top + rectA.height / 2 + startYOffset; const endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset; const endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset; const controlY = startY - curvature; const d = `M ${startX},${startY} Q ${ (startX + endX) / 2 },${controlY} ${endX},${endY}`; setPathD(d); } }; // Initialize ResizeObserver const resizeObserver = new ResizeObserver((entries) => { // For all entries, recalculate the path for (let entry of entries) { updatePath(); } }); // Observe the container element if (containerRef.current) { resizeObserver.observe(containerRef.current); } // Call the updatePath initially to set the initial path updatePath(); // Clean up the observer on component unmount return () => { resizeObserver.disconnect(); }; }, [ containerRef, fromRef, toRef, curvature, startXOffset, startYOffset, endXOffset, endYOffset, ]); return ( <svg fill="none" width={svgDimensions.width} height={svgDimensions.height} xmlns="http://www.w3.org/2000/svg" className={cn( "pointer-events-none absolute left-0 top-0 transform-gpu stroke-2", className, )} viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`} > <path d={pathD} stroke={pathColor} strokeWidth={pathWidth} strokeOpacity={pathOpacity} strokeLinecap="round" /> <path d={pathD} strokeWidth={pathWidth} stroke={`url(#${id})`} strokeOpacity="1" strokeLinecap="round" /> <defs> <motion.linearGradient className="transform-gpu" id={id} gradientUnits={"userSpaceOnUse"} initial={{ x1: "0%", x2: "0%", y1: "0%", y2: "0%", }} animate={{ x1: gradientCoordinates.x1, x2: gradientCoordinates.x2, y1: gradientCoordinates.y1, y2: gradientCoordinates.y2, }} transition={{ delay, duration, ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo repeat: Infinity, repeatDelay: 0, }} > <stop stopColor={gradientStartColor} stopOpacity="0"></stop> <stop stopColor={gradientStartColor}></stop> <stop offset="32.5%" stopColor={gradientStopColor}></stop> <stop offset="100%" stopColor={gradientStopColor} stopOpacity="0" ></stop> </motion.linearGradient> </defs> </svg> );};
Installation
npx shadcn@latest add https://www.shadcn.io/registry/animated-beam.json
npx shadcn@latest add https://www.shadcn.io/registry/animated-beam.json
pnpm dlx shadcn@latest add https://www.shadcn.io/registry/animated-beam.json
bunx shadcn@latest add https://www.shadcn.io/registry/animated-beam.json
Features
- SVG path animations - Smooth curved and straight beam connections using Motion library
- Bidirectional flow - Reverse animation support for complex data visualizations using JavaScript
- Automatic positioning - Dynamic path calculation between elements using React refs
- Customizable gradients - Color, timing, curvature, and offset controls using TypeScript props
- Responsive design - ResizeObserver integration for container updates in Next.js applications
- Performance optimized - GPU-accelerated transforms with efficient rendering using Tailwind CSS
- Accessibility features - ARIA attributes and reduced motion support using shadcn/ui patterns
- Open source - Free component with comprehensive API and documentation
Examples
Bidirectional Beam
"use client";import React, { forwardRef, useRef } from "react";import { cn } from "@/lib/utils";import { AnimatedBeam } from "@/components/ui/shadcn-io/animated-beam";const Circle = forwardRef< HTMLDivElement, { className?: string; children?: React.ReactNode }>(({ className, children }, ref) => { return ( <div ref={ref} className={cn( "z-10 flex size-12 items-center justify-center rounded-full border-2 bg-white p-3 shadow-[0_0_20px_-12px_rgba(0,0,0,0.8)]", className, )} > {children} </div> );});Circle.displayName = "Circle";const Example = () => { const containerRef = useRef<HTMLDivElement>(null); const div1Ref = useRef<HTMLDivElement>(null); const div2Ref = useRef<HTMLDivElement>(null); return ( <div className="relative flex w-full max-w-[500px] items-center justify-center overflow-hidden p-10" ref={containerRef} > <div className="flex size-full flex-col items-stretch justify-between gap-10"> <div className="flex flex-row justify-between"> <Circle ref={div1Ref}> <Icons.user /> </Circle> <Circle ref={div2Ref}> <Icons.openai /> </Circle> </div> </div> <AnimatedBeam containerRef={containerRef} fromRef={div1Ref} toRef={div2Ref} startYOffset={10} endYOffset={10} curvature={-20} /> <AnimatedBeam containerRef={containerRef} fromRef={div1Ref} toRef={div2Ref} startYOffset={-10} endYOffset={-10} curvature={20} reverse /> </div> );};const Icons = { openai: () => ( <svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" /> </svg> ), user: () => ( <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" strokeWidth="2" xmlns="http://www.w3.org/2000/svg" > <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /> <circle cx="12" cy="7" r="4" /> </svg> ),};export default Example;
"use client";import { motion } from "motion/react";import { RefObject, useEffect, useId, useState } from "react";import { cn } from "@/lib/utils";export interface AnimatedBeamProps { className?: string; containerRef: RefObject<HTMLElement | null>; // Container ref fromRef: RefObject<HTMLElement | null>; toRef: RefObject<HTMLElement | null>; curvature?: number; reverse?: boolean; pathColor?: string; pathWidth?: number; pathOpacity?: number; gradientStartColor?: string; gradientStopColor?: string; delay?: number; duration?: number; startXOffset?: number; startYOffset?: number; endXOffset?: number; endYOffset?: number;}export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({ className, containerRef, fromRef, toRef, curvature = 0, reverse = false, // Include the reverse prop duration = Math.random() * 3 + 4, delay = 0, pathColor = "gray", pathWidth = 2, pathOpacity = 0.2, gradientStartColor = "#ffaa40", gradientStopColor = "#9c40ff", startXOffset = 0, startYOffset = 0, endXOffset = 0, endYOffset = 0,}) => { const id = useId(); const [pathD, setPathD] = useState(""); const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 }); // Calculate the gradient coordinates based on the reverse prop const gradientCoordinates = reverse ? { x1: ["90%", "-10%"], x2: ["100%", "0%"], y1: ["0%", "0%"], y2: ["0%", "0%"], } : { x1: ["10%", "110%"], x2: ["0%", "100%"], y1: ["0%", "0%"], y2: ["0%", "0%"], }; useEffect(() => { const updatePath = () => { if (containerRef.current && fromRef.current && toRef.current) { const containerRect = containerRef.current.getBoundingClientRect(); const rectA = fromRef.current.getBoundingClientRect(); const rectB = toRef.current.getBoundingClientRect(); const svgWidth = containerRect.width; const svgHeight = containerRect.height; setSvgDimensions({ width: svgWidth, height: svgHeight }); const startX = rectA.left - containerRect.left + rectA.width / 2 + startXOffset; const startY = rectA.top - containerRect.top + rectA.height / 2 + startYOffset; const endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset; const endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset; const controlY = startY - curvature; const d = `M ${startX},${startY} Q ${ (startX + endX) / 2 },${controlY} ${endX},${endY}`; setPathD(d); } }; // Initialize ResizeObserver const resizeObserver = new ResizeObserver((entries) => { // For all entries, recalculate the path for (let entry of entries) { updatePath(); } }); // Observe the container element if (containerRef.current) { resizeObserver.observe(containerRef.current); } // Call the updatePath initially to set the initial path updatePath(); // Clean up the observer on component unmount return () => { resizeObserver.disconnect(); }; }, [ containerRef, fromRef, toRef, curvature, startXOffset, startYOffset, endXOffset, endYOffset, ]); return ( <svg fill="none" width={svgDimensions.width} height={svgDimensions.height} xmlns="http://www.w3.org/2000/svg" className={cn( "pointer-events-none absolute left-0 top-0 transform-gpu stroke-2", className, )} viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`} > <path d={pathD} stroke={pathColor} strokeWidth={pathWidth} strokeOpacity={pathOpacity} strokeLinecap="round" /> <path d={pathD} strokeWidth={pathWidth} stroke={`url(#${id})`} strokeOpacity="1" strokeLinecap="round" /> <defs> <motion.linearGradient className="transform-gpu" id={id} gradientUnits={"userSpaceOnUse"} initial={{ x1: "0%", x2: "0%", y1: "0%", y2: "0%", }} animate={{ x1: gradientCoordinates.x1, x2: gradientCoordinates.x2, y1: gradientCoordinates.y1, y2: gradientCoordinates.y2, }} transition={{ delay, duration, ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo repeat: Infinity, repeatDelay: 0, }} > <stop stopColor={gradientStartColor} stopOpacity="0"></stop> <stop stopColor={gradientStartColor}></stop> <stop offset="32.5%" stopColor={gradientStopColor}></stop> <stop offset="100%" stopColor={gradientStopColor} stopOpacity="0" ></stop> </motion.linearGradient> </defs> </svg> );};
Multiple Outputs
"use client";import React, { forwardRef, useRef } from "react";import { cn } from "@/lib/utils";import { AnimatedBeam } from "@/components/ui/shadcn-io/animated-beam";const Circle = forwardRef< HTMLDivElement, { className?: string; children?: React.ReactNode }>(({ className, children }, ref) => { return ( <div ref={ref} className={cn( "z-10 flex size-12 items-center justify-center rounded-full border-2 bg-white p-3 shadow-[0_0_20px_-12px_rgba(0,0,0,0.8)]", className, )} > {children} </div> );});Circle.displayName = "Circle";const Example = () => { const containerRef = useRef<HTMLDivElement>(null); const div1Ref = useRef<HTMLDivElement>(null); const div2Ref = useRef<HTMLDivElement>(null); const div3Ref = useRef<HTMLDivElement>(null); const div4Ref = useRef<HTMLDivElement>(null); const div5Ref = useRef<HTMLDivElement>(null); const div6Ref = useRef<HTMLDivElement>(null); const div7Ref = useRef<HTMLDivElement>(null); return ( <div className="relative flex h-[500px] w-full items-center justify-center overflow-hidden p-10" ref={containerRef} > <div className="flex size-full max-w-lg flex-row items-stretch justify-between gap-10"> <div className="flex flex-col justify-center"> <Circle ref={div7Ref}> <Icons.user /> </Circle> </div> <div className="flex flex-col justify-center"> <Circle ref={div6Ref} className="size-16"> <Icons.openai /> </Circle> </div> <div className="flex flex-col justify-center gap-2"> <Circle ref={div1Ref}> <Icons.googleDrive /> </Circle> <Circle ref={div2Ref}> <Icons.googleDocs /> </Circle> <Circle ref={div3Ref}> <Icons.whatsapp /> </Circle> <Circle ref={div4Ref}> <Icons.messenger /> </Circle> <Circle ref={div5Ref}> <Icons.notion /> </Circle> </div> </div> <AnimatedBeam containerRef={containerRef} fromRef={div1Ref} toRef={div6Ref} duration={3} /> <AnimatedBeam containerRef={containerRef} fromRef={div2Ref} toRef={div6Ref} duration={3} /> <AnimatedBeam containerRef={containerRef} fromRef={div3Ref} toRef={div6Ref} duration={3} /> <AnimatedBeam containerRef={containerRef} fromRef={div4Ref} toRef={div6Ref} duration={3} /> <AnimatedBeam containerRef={containerRef} fromRef={div5Ref} toRef={div6Ref} duration={3} /> <AnimatedBeam containerRef={containerRef} fromRef={div6Ref} toRef={div7Ref} duration={3} /> </div> );};const Icons = { notion: () => ( <svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z" fill="#ffffff" /> <path d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.723 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z" fill="#000000" fillRule="evenodd" clipRule="evenodd" /> </svg> ), openai: () => ( <svg width="100" height="100" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" /> </svg> ), googleDrive: () => ( <svg width="100" height="100" viewBox="0 0 87.3 78" xmlns="http://www.w3.org/2000/svg" > <path d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z" fill="#0066da" /> <path d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z" fill="#00ac47" /> <path d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z" fill="#ea4335" /> <path d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z" fill="#00832d" /> <path d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z" fill="#2684fc" /> <path d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z" fill="#ffba00" /> </svg> ), whatsapp: () => ( <svg width="100" height="100" viewBox="0 0 175.216 175.552" xmlns="http://www.w3.org/2000/svg" > <defs> <linearGradient id="b" x1="85.915" x2="86.535" y1="32.567" y2="137.092" gradientUnits="userSpaceOnUse" > <stop offset="0" stopColor="#57d163" /> <stop offset="1" stopColor="#23b33a" /> </linearGradient> <filter id="a" width="1.115" height="1.114" x="-.057" y="-.057" colorInterpolationFilters="sRGB" > <feGaussianBlur stdDeviation="3.531" /> </filter> </defs> <path d="m54.532 138.45 2.235 1.324c9.387 5.571 20.15 8.518 31.126 8.523h.023c33.707 0 61.139-27.426 61.153-61.135.006-16.335-6.349-31.696-17.895-43.251A60.75 60.75 0 0 0 87.94 25.983c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.312-6.179 22.558zm-40.811 23.544L24.16 123.88c-6.438-11.154-9.825-23.808-9.821-36.772.017-40.556 33.021-73.55 73.578-73.55 19.681.01 38.154 7.669 52.047 21.572s21.537 32.383 21.53 52.037c-.018 40.553-33.027 73.553-73.578 73.553h-.032c-12.313-.005-24.412-3.094-35.159-8.954zm0 0" fill="#b3b3b3" filter="url(#a)" /> <path d="m12.966 161.238 10.439-38.114a73.42 73.42 0 0 1-9.821-36.772c.017-40.556 33.021-73.55 73.578-73.55 19.681.01 38.154 7.669 52.047 21.572s21.537 32.383 21.53 52.037c-.018 40.553-33.027 73.553-73.578 73.553h-.032c-12.313-.005-24.412-3.094-35.159-8.954z" fill="#ffffff" /> <path d="M87.184 25.227c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.312-6.179 22.559 23.146-6.069 2.235 1.324c9.387 5.571 20.15 8.518 31.126 8.524h.023c33.707 0 61.14-27.426 61.153-61.135a60.75 60.75 0 0 0-17.895-43.251 60.75 60.75 0 0 0-43.235-17.929z" fill="url(#linearGradient1780)" /> <path d="M87.184 25.227c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.313-6.179 22.558 23.146-6.069 2.235 1.324c9.387 5.571 20.15 8.517 31.126 8.523h.023c33.707 0 61.14-27.426 61.153-61.135a60.75 60.75 0 0 0-17.895-43.251 60.75 60.75 0 0 0-43.235-17.928z" fill="url(#b)" /> <path d="M68.772 55.603c-1.378-3.061-2.828-3.123-4.137-3.176l-3.524-.043c-1.226 0-3.218.46-4.902 2.3s-6.435 6.287-6.435 15.332 6.588 17.785 7.506 19.013 12.718 20.381 31.405 27.75c15.529 6.124 18.689 4.906 22.061 4.6s10.877-4.447 12.408-8.74 1.532-7.971 1.073-8.74-1.685-1.226-3.525-2.146-10.877-5.367-12.562-5.981-2.91-.919-4.137.921-4.746 5.979-5.819 7.206-2.144 1.381-3.984.462-7.76-2.861-14.784-9.124c-5.465-4.873-9.154-10.891-10.228-12.73s-.114-2.835.808-3.751c.825-.824 1.838-2.147 2.759-3.22s1.224-1.84 1.836-3.065.307-2.301-.153-3.22-4.032-10.011-5.666-13.647" fill="#ffffff" fillRule="evenodd" /> </svg> ), googleDocs: () => ( <svg width="47px" height="65px" viewBox="0 0 47 65" xmlns="http://www.w3.org/2000/svg" > <defs> <path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-1" /> <path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-3" /> <linearGradient x1="50.0053945%" y1="8.58610612%" x2="50.0053945%" y2="100.013939%" id="linearGradient-5" > <stop stopColor="#1A237E" stopOpacity="0.2" offset="0%" /> <stop stopColor="#1A237E" stopOpacity="0.02" offset="100%" /> </linearGradient> <path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-6" /> <path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-8" /> <path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-10" /> <path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-12" /> <path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-14" /> <radialGradient cx="3.16804688%" cy="2.71744318%" fx="3.16804688%" fy="2.71744318%" r="161.248516%" gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.723077),translate(-0.031680,-0.027174)" id="radialGradient-16" > <stop stopColor="#FFFFFF" stopOpacity="0.1" offset="0%" /> <stop stopColor="#FFFFFF" stopOpacity="0" offset="100%" /> </radialGradient> </defs> <g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd" > <g transform="translate(-451.000000, -463.000000)"> <g id="Hero" transform="translate(0.000000, 63.000000)"> <g id="Personal" transform="translate(277.000000, 309.000000)"> <g id="Docs-icon" transform="translate(174.000000, 91.000000)"> <g id="Group"> <g id="Clipped"> <mask id="mask-2" fill="white"> <use xlinkHref="#path-1" /> </mask> <g id="SVGID_1_" /> <path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L36.71875,10.3409091 L29.375,0 Z" id="Path" fill="#4285F4" fillRule="nonzero" mask="url(#mask-2)" /> </g> <g id="Clipped"> <mask id="mask-4" fill="white"> <use xlinkHref="#path-3" /> </mask> <g id="SVGID_1_" /> <polygon id="Path" fill="url(#linearGradient-5)" fillRule="nonzero" mask="url(#mask-4)" points="30.6638281 16.4309659 47 32.8582386 47 17.7272727" ></polygon> </g> <g id="Clipped"> <mask id="mask-7" fill="white"> <use xlinkHref="#path-6" /> </mask> <g id="SVGID_1_" /> <path d="M11.75,47.2727273 L35.25,47.2727273 L35.25,44.3181818 L11.75,44.3181818 L11.75,47.2727273 Z M11.75,53.1818182 L29.375,53.1818182 L29.375,50.2272727 L11.75,50.2272727 L11.75,53.1818182 Z M11.75,32.5 L11.75,35.4545455 L35.25,35.4545455 L35.25,32.5 L11.75,32.5 Z M11.75,41.3636364 L35.25,41.3636364 L35.25,38.4090909 L11.75,38.4090909 L11.75,41.3636364 Z" id="Shape" fill="#F1F1F1" fillRule="nonzero" mask="url(#mask-7)" /> </g> <g id="Clipped"> <mask id="mask-9" fill="white"> <use xlinkHref="#path-8" /> </mask> <g id="SVGID_1_" /> <g id="Group" mask="url(#mask-9)"> <g transform="translate(26.437500, -2.954545)"> <path d="M2.9375,2.95454545 L2.9375,16.25 C2.9375,18.6985795 4.90929688,20.6818182 7.34375,20.6818182 L20.5625,20.6818182 L2.9375,2.95454545 Z" id="Path" fill="#A1C2FA" fillRule="nonzero" /> </g> </g> </g> <g id="Clipped"> <mask id="mask-11" fill="white"> <use xlinkHref="#path-10" /> </mask> <g id="SVGID_1_" /> <path d="M4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,4.80113636 C0,2.36363636 1.9828125,0.369318182 4.40625,0.369318182 L29.375,0.369318182 L29.375,0 L4.40625,0 Z" id="Path" fillOpacity="0.2" fill="#FFFFFF" fillRule="nonzero" mask="url(#mask-11)" /> </g> <g id="Clipped"> <mask id="mask-13" fill="white"> <use xlinkHref="#path-12" /> </mask> <g id="SVGID_1_" /> <path d="M42.59375,64.6306818 L4.40625,64.6306818 C1.9828125,64.6306818 0,62.6363636 0,60.1988636 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,60.1988636 C47,62.6363636 45.0171875,64.6306818 42.59375,64.6306818 Z" id="Path" fillOpacity="0.2" fill="#1A237E" fillRule="nonzero" mask="url(#mask-13)" /> </g> <g id="Clipped"> <mask id="mask-15" fill="white"> <use xlinkHref="#path-14" /> </mask> <g id="SVGID_1_" /> <path d="M33.78125,17.7272727 C31.3467969,17.7272727 29.375,15.7440341 29.375,13.2954545 L29.375,13.6647727 C29.375,16.1133523 31.3467969,18.0965909 33.78125,18.0965909 L47,18.0965909 L47,17.7272727 L33.78125,17.7272727 Z" id="Path" fillOpacity="0.1" fill="#1A237E" fillRule="nonzero" mask="url(#mask-15)" /> </g> </g> <path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="Path" fill="url(#radialGradient-16)" fillRule="nonzero" /> </g> </g> </g> </g> </g> </svg> ), messenger: () => ( <svg width="100" height="100" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" > <radialGradient id="8O3wK6b5ASW2Wn6hRCB5xa_YFbzdUk7Q3F8_gr1" cx="11.087" cy="7.022" r="47.612" gradientTransform="matrix(1 0 0 -1 0 50)" gradientUnits="userSpaceOnUse" > <stop offset="0" stopColor="#1292ff"></stop> <stop offset=".079" stopColor="#2982ff"></stop> <stop offset=".23" stopColor="#4e69ff"></stop> <stop offset=".351" stopColor="#6559ff"></stop> <stop offset=".428" stopColor="#6d53ff"></stop> <stop offset=".754" stopColor="#df47aa"></stop> <stop offset=".946" stopColor="#ff6257"></stop> </radialGradient> <path fill="url(#8O3wK6b5ASW2Wn6hRCB5xa_YFbzdUk7Q3F8_gr1)" d="M44,23.5C44,34.27,35.05,43,24,43c-1.651,0-3.25-0.194-4.784-0.564 c-0.465-0.112-0.951-0.069-1.379,0.145L13.46,44.77C12.33,45.335,11,44.513,11,43.249v-4.025c0-0.575-0.257-1.111-0.681-1.499 C6.425,34.165,4,29.11,4,23.5C4,12.73,12.95,4,24,4S44,12.73,44,23.5z" /> <path d="M34.992,17.292c-0.428,0-0.843,0.142-1.2,0.411l-5.694,4.215 c-0.133,0.1-0.28,0.15-0.435,0.15c-0.15,0-0.291-0.047-0.41-0.136l-3.972-2.99c-0.808-0.601-1.76-0.918-2.757-0.918 c-1.576,0-3.025,0.791-3.876,2.116l-1.211,1.891l-4.12,6.695c-0.392,0.614-0.422,1.372-0.071,2.014 c0.358,0.654,1.034,1.06,1.764,1.06c0.428,0,0.843-0.142,1.2-0.411l5.694-4.215c0.133-0.1,0.28-0.15,0.435-0.15 c0.15,0,0.291,0.047,0.41,0.136l3.972,2.99c0.809,0.602,1.76,0.918,2.757,0.918c1.576,0,3.025-0.791,3.876-2.116l1.211-1.891 l4.12-6.695c0.392-0.614,0.422-1.372,0.071-2.014C36.398,17.698,35.722,17.292,34.992,17.292L34.992,17.292z" opacity=".05" /> <path d="M34.992,17.792c-0.319,0-0.63,0.107-0.899,0.31l-5.697,4.218 c-0.216,0.163-0.468,0.248-0.732,0.248c-0.259,0-0.504-0.082-0.71-0.236l-3.973-2.991c-0.719-0.535-1.568-0.817-2.457-0.817 c-1.405,0-2.696,0.705-3.455,1.887l-1.21,1.891l-4.115,6.688c-0.297,0.465-0.32,1.033-0.058,1.511c0.266,0.486,0.787,0.8,1.325,0.8 c0.319,0,0.63-0.107,0.899-0.31l5.697-4.218c0.216-0.163,0.468-0.248,0.732-0.248c0.259,0,0.504,0.082,0.71,0.236l3.973,2.991 c0.719,0.535,1.568,0.817,2.457,0.817c1.405,0,2.696-0.705,3.455-1.887l1.21-1.891l4.115-6.688c0.297-0.465,0.32-1.033,0.058-1.511 C36.051,18.106,35.531,17.792,34.992,17.792L34.992,17.792z" opacity=".07" /> <path fill="#ffffff" d="M34.394,18.501l-5.7,4.22c-0.61,0.46-1.44,0.46-2.04,0.01L22.68,19.74 c-1.68-1.25-4.06-0.82-5.19,0.94l-1.21,1.89l-4.11,6.68c-0.6,0.94,0.55,2.01,1.44,1.34l5.7-4.22c0.61-0.46,1.44-0.46,2.04-0.01 l3.974,2.991c1.68,1.25,4.06,0.82,5.19-0.94l1.21-1.89l4.11-6.68C36.434,18.901,35.284,17.831,34.394,18.501z" /> </svg> ), user: () => ( <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" strokeWidth="2" xmlns="http://www.w3.org/2000/svg" > <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /> <circle cx="12" cy="7" r="4" /> </svg> ),};export default Example;
"use client";import { motion } from "motion/react";import { RefObject, useEffect, useId, useState } from "react";import { cn } from "@/lib/utils";export interface AnimatedBeamProps { className?: string; containerRef: RefObject<HTMLElement | null>; // Container ref fromRef: RefObject<HTMLElement | null>; toRef: RefObject<HTMLElement | null>; curvature?: number; reverse?: boolean; pathColor?: string; pathWidth?: number; pathOpacity?: number; gradientStartColor?: string; gradientStopColor?: string; delay?: number; duration?: number; startXOffset?: number; startYOffset?: number; endXOffset?: number; endYOffset?: number;}export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({ className, containerRef, fromRef, toRef, curvature = 0, reverse = false, // Include the reverse prop duration = Math.random() * 3 + 4, delay = 0, pathColor = "gray", pathWidth = 2, pathOpacity = 0.2, gradientStartColor = "#ffaa40", gradientStopColor = "#9c40ff", startXOffset = 0, startYOffset = 0, endXOffset = 0, endYOffset = 0,}) => { const id = useId(); const [pathD, setPathD] = useState(""); const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 }); // Calculate the gradient coordinates based on the reverse prop const gradientCoordinates = reverse ? { x1: ["90%", "-10%"], x2: ["100%", "0%"], y1: ["0%", "0%"], y2: ["0%", "0%"], } : { x1: ["10%", "110%"], x2: ["0%", "100%"], y1: ["0%", "0%"], y2: ["0%", "0%"], }; useEffect(() => { const updatePath = () => { if (containerRef.current && fromRef.current && toRef.current) { const containerRect = containerRef.current.getBoundingClientRect(); const rectA = fromRef.current.getBoundingClientRect(); const rectB = toRef.current.getBoundingClientRect(); const svgWidth = containerRect.width; const svgHeight = containerRect.height; setSvgDimensions({ width: svgWidth, height: svgHeight }); const startX = rectA.left - containerRect.left + rectA.width / 2 + startXOffset; const startY = rectA.top - containerRect.top + rectA.height / 2 + startYOffset; const endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset; const endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset; const controlY = startY - curvature; const d = `M ${startX},${startY} Q ${ (startX + endX) / 2 },${controlY} ${endX},${endY}`; setPathD(d); } }; // Initialize ResizeObserver const resizeObserver = new ResizeObserver((entries) => { // For all entries, recalculate the path for (let entry of entries) { updatePath(); } }); // Observe the container element if (containerRef.current) { resizeObserver.observe(containerRef.current); } // Call the updatePath initially to set the initial path updatePath(); // Clean up the observer on component unmount return () => { resizeObserver.disconnect(); }; }, [ containerRef, fromRef, toRef, curvature, startXOffset, startYOffset, endXOffset, endYOffset, ]); return ( <svg fill="none" width={svgDimensions.width} height={svgDimensions.height} xmlns="http://www.w3.org/2000/svg" className={cn( "pointer-events-none absolute left-0 top-0 transform-gpu stroke-2", className, )} viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`} > <path d={pathD} stroke={pathColor} strokeWidth={pathWidth} strokeOpacity={pathOpacity} strokeLinecap="round" /> <path d={pathD} strokeWidth={pathWidth} stroke={`url(#${id})`} strokeOpacity="1" strokeLinecap="round" /> <defs> <motion.linearGradient className="transform-gpu" id={id} gradientUnits={"userSpaceOnUse"} initial={{ x1: "0%", x2: "0%", y1: "0%", y2: "0%", }} animate={{ x1: gradientCoordinates.x1, x2: gradientCoordinates.x2, y1: gradientCoordinates.y1, y2: gradientCoordinates.y2, }} transition={{ delay, duration, ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo repeat: Infinity, repeatDelay: 0, }} > <stop stopColor={gradientStartColor} stopOpacity="0"></stop> <stop stopColor={gradientStartColor}></stop> <stop offset="32.5%" stopColor={gradientStopColor}></stop> <stop offset="100%" stopColor={gradientStopColor} stopOpacity="0" ></stop> </motion.linearGradient> </defs> </svg> );};
API Reference
AnimatedBeam
Prop | Type | Default | Description |
---|---|---|---|
className | string | undefined | Additional CSS class names to apply to the beam component |
containerRef | RefObject<HTMLElement> | required | Ref to the container element that wraps the beam and its connected elements |
fromRef | RefObject<HTMLElement> | required | Ref to the source element where the beam animation starts |
toRef | RefObject<HTMLElement> | required | Ref to the target element where the beam animation ends |
curvature | number | 0 | Controls the curve of the beam path. Positive values curve upward, negative values curve downward |
reverse | boolean | false | Reverses the animation direction, making the beam flow from target to source |
duration | number | Math.random() * 3 + 4 | Animation duration in seconds. Random by default for organic feel |
delay | number | 0 | Delay before animation starts in seconds |
pathColor | string | "gray" | Color of the static beam path background |
pathWidth | number | 2 | Width of the beam path in pixels |
pathOpacity | number | 0.2 | Opacity of the static beam path (0-1) |
gradientStartColor | string | "#ffaa40" | Starting color of the animated gradient beam |
gradientStopColor | string | "#9c40ff" | Ending color of the animated gradient beam |
startXOffset | number | 0 | Horizontal offset from the source element center in pixels |
startYOffset | number | 0 | Vertical offset from the source element center in pixels |
endXOffset | number | 0 | Horizontal offset from the target element center in pixels |
endYOffset | number | 0 | Vertical offset from the target element center in pixels |
Use Cases
- Data flow diagrams - Visualize data movement between services and components
- Process workflows - Illustrate step-by-step operations with animated connections
- Dashboard connections - Link widgets and metrics with visual relationships
- System architecture - Show communication paths in technical diagrams
Implementation
Built with Motion for smooth SVG animations. Uses React refs for automatic positioning. Supports custom gradients and bidirectional flow. ResizeObserver handles responsive updates.
Status Dashboard Navigation Bar
System monitoring navigation bar with status indicators, performance metrics, and power control. Features real-time status badges, uptime tracking, latency monitoring, and animated power toggle for infrastructure dashboards.
Magnetic
Interactive magnetic attraction effects that follow cursor movement. Perfect for React applications requiring engaging hover animations with Next.js integration and TypeScript support.