Border Beam
ui-beats/border-beamFreeComponent
The BorderBeam component wraps any content in a border traced by a travelling beam of light, following the container's rounded corners exactly and leaving the page around it completely still.
Install it
npx shadcn@latest add https://uibeats.com/r/border-beam.jsonSource
1"use client";23import {4 useAnimationFrame,5 motion,6 useMotionTemplate,7 useMotionValue,8 useReducedMotion,9} from "motion/react";10import { type ReactNode } from "react";11import { cn } from "@/lib/utils";1213interface BorderBeamProps {14 children: ReactNode;15 /** Thickness of the animated border, in pixels. */16 borderWidth?: number;17 /** Seconds for the beam to travel once around the element. */18 duration?: number;19 /** Leading colour of the beam. */20 colorFrom?: string;21 /** Trailing colour, faded out into the border. */22 colorTo?: string;23 /** Arc the beam covers, in degrees. Smaller reads as a sharper streak. */24 arc?: number;25 /** Paint a blurred copy underneath, so the beam bleeds onto the page. */26 glow?: boolean;27 className?: string;28}2930/**31 * A container whose border is traced by a travelling beam of light.32 *33 * The ring is a single element carrying a conic gradient, masked so only the34 * border area paints: `mask-composite` subtracts the content box from the35 * padding box, leaving a band exactly `borderWidth` thick. That is why the36 * beam follows rounded corners exactly — it is the element's own shape, not a37 * dot being animated around an approximation of it.38 *39 * The angle is driven by a motion value written from `useAnimationFrame`40 * rather than a React state tick, so the beam costs one style write per frame41 * and never re-renders the subtree it wraps.42 */43export function BorderBeam({44 children,45 borderWidth = 1.5,46 duration = 6,47 colorFrom = "#a855f7",48 colorTo = "#22d3ee",49 arc = 70,50 glow = true,51 className = "",52}: BorderBeamProps) {53 const prefersReducedMotion = useReducedMotion();54 const angle = useMotionValue(0);5556 useAnimationFrame((elapsed) => {57 if (prefersReducedMotion) return;58 // `elapsed` is milliseconds since the component mounted, so the beam is59 // continuous across re-renders and never jumps when props change.60 angle.set((elapsed / (duration * 1000)) * 360);61 });6263 // Transparent everywhere except the arc, which fades from the head colour64 // into the tail colour and then out — a streak rather than a hard wedge.65 const stops = `transparent 0deg, ${colorTo} ${arc * 0.45}deg, ${colorFrom} ${arc}deg, transparent ${arc + 0.5}deg`;66 const background = useMotionTemplate`conic-gradient(from ${angle}deg at 50% 50%, ${stops})`;6768 const ring = {69 background,70 padding: borderWidth,71 // Keep only the padding-box ring: paint the whole box, then punch out72// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from ui-beats
All 34 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Beamanimated-beam | ui-beats | Components | Free | 1 dep | |
| Animated Listanimated-list | ui-beats | Components | Free | 1 dep | |
| Bouncebounce | ui-beats | Components | Free | 1 dep | |
| Card Stackcard-stack | ui-beats | Components | Free | 1 dep | |
| Dockdock | ui-beats | Components | Free | 1 dep | |
| Fade Infade-in | ui-beats | Components | Free | 1 dep | |
| Fade In Unblurfade-in-unblur | ui-beats | Components | Free | 1 dep | |
| Flip Cardflip-card | ui-beats | Components | Free | 2 deps |
