Velora UI
All components

Border Beam

A beam of light traveling around any border.

0.9 KB gzippedDeps: motion/reactReduced-motion safeBase UI & Radix compatible

<BorderBeam />

Installation

npx shadcn@latest add https://velora.colorlib.com/r/border-beam.json

Installs with: motion

Source

"use client";

import { motion, useReducedMotion } from "motion/react";

import { cn } from "@/lib/utils";

interface BorderBeamProps {
  className?: string;
  /** Size of the beam in px */
  size?: number;
  /** Seconds for one full loop */
  duration?: number;
  /** Seconds of delay before starting */
  delay?: number;
  reverse?: boolean;
  colorFrom?: string;
  colorTo?: string;
}

/**
 * Animated beam that travels around the border of its nearest
 * positioned ancestor. Parent needs `position: relative` and a border.
 */
export function BorderBeam({
  className,
  size = 64,
  duration = 6,
  delay = 0,
  reverse = false,
  colorFrom = "var(--brand-from)",
  colorTo = "var(--brand-to)",
}: BorderBeamProps) {
  // Hide via CSS + skip the animation under reduced motion; never branch the
  // rendered tree on it — the server can't know the preference and would
  // mismatch on hydration.
  const reducedMotion = useReducedMotion();

  return (
    <div
      aria-hidden
      data-slot="border-beam"
      className="pointer-events-none absolute inset-0 rounded-[inherit] border border-transparent [mask-clip:padding-box,border-box] [mask-composite:intersect] [mask-image:linear-gradient(transparent,transparent),linear-gradient(#000,#000)] motion-reduce:hidden"
    >
      <motion.div
        className={cn(
          "absolute aspect-square bg-gradient-to-l from-(--beam-from) via-(--beam-to) to-transparent",
          className
        )}
        style={
          {
            width: size,
            offsetPath: `rect(0 auto auto 0 round ${size}px)`,
            "--beam-from": colorFrom,
            "--beam-to": colorTo,
          } as React.CSSProperties
        }
        initial={{ offsetDistance: reverse ? "100%" : "0%" }}
        animate={
          reducedMotion
            ? undefined
            : { offsetDistance: reverse ? "0%" : "100%" }
        }
        transition={{
          repeat: Infinity,
          ease: "linear",
          duration,
          delay: -delay,
        }}
      />
    </div>
  );
}