# Border Beam

The BorderBeam component wraps any content in a border traced by a travelling beam of light. It follows the container's rounded corners exactly, and nothing around it moves.

- **Category:** Component
- **Docs:** https://uibeats.com/docs/component/border-beam
- **Registry:** https://uibeats.com/r/border-beam.json
- **License:** MIT
- **npm dependencies:** `lucide-react`, `motion`

## Installation

```bash
npx shadcn@latest add https://uibeats.com/r/border-beam.json
```

Or install the dependencies and copy the source below:

```bash
npm install lucide-react motion
```

## When to use

For the one element you want looked at first: a recommended tier, a featured card, a call to action. Keep it to one per screen.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `React.ReactNode` | `-` | Content rendered inside the bordered container |
| `borderWidth` | `number` | `1.5` | Thickness of the animated border, in pixels |
| `duration` | `number` | `6` | Seconds for the beam to travel once around the element |
| `colorFrom` | `string` | `"#a855f7"` | Leading colour of the beam |
| `colorTo` | `string` | `"#22d3ee"` | Trailing colour, faded out into the border |
| `arc` | `number` | `70` | Degrees of the border the beam covers; smaller reads as a sharper streak |
| `glow` | `boolean` | `true` | Paint a blurred copy underneath so the beam bleeds onto the page |
| `className` | `string` | `''` | Additional CSS classes applied to the outer container |

## Example usage

```tsx
"use client";

import { Sparkles } from "lucide-react";
import { BorderBeam } from "@/components/demo/component/border-beam";

const BorderBeamUsage = () => {
  return (
    <BorderBeam className="w-72" duration={5}>
      <div className="p-6">
        <div className="flex size-9 items-center justify-center rounded-lg bg-brand-subtle text-brand">
          <Sparkles className="size-4" />
        </div>
        <h3 className="mt-4 text-sm font-semibold">Pro plan</h3>
        <p className="mt-1 text-xs leading-relaxed text-muted-foreground">
          A light travels the border to mark the card worth looking at, without
          moving anything on the page.
        </p>
      </div>
    </BorderBeam>
  );
};

export default BorderBeamUsage;
```

## Source

Save as `components/ui/border-beam.tsx`.

```tsx
"use client";

import {
  useAnimationFrame,
  motion,
  useMotionTemplate,
  useMotionValue,
  useReducedMotion,
} from "motion/react";
import { type ReactNode } from "react";
import { cn } from "@/lib/utils";

interface BorderBeamProps {
  children: ReactNode;
  /** Thickness of the animated border, in pixels. */
  borderWidth?: number;
  /** Seconds for the beam to travel once around the element. */
  duration?: number;
  /** Leading colour of the beam. */
  colorFrom?: string;
  /** Trailing colour, faded out into the border. */
  colorTo?: string;
  /** Arc the beam covers, in degrees. Smaller reads as a sharper streak. */
  arc?: number;
  /** Paint a blurred copy underneath, so the beam bleeds onto the page. */
  glow?: boolean;
  className?: string;
}

/**
 * A container whose border is traced by a travelling beam of light.
 *
 * The ring is a single element carrying a conic gradient, masked so only the
 * border area paints: `mask-composite` subtracts the content box from the
 * padding box, leaving a band exactly `borderWidth` thick. That is why the
 * beam follows rounded corners exactly: it is the element's own shape, not a
 * dot being animated around an approximation of it.
 *
 * The angle is driven by a motion value written from `useAnimationFrame`
 * rather than a React state tick, so the beam costs one style write per frame
 * and never re-renders the subtree it wraps.
 */
export function BorderBeam({
  children,
  borderWidth = 1.5,
  duration = 6,
  colorFrom = "#a855f7",
  colorTo = "#22d3ee",
  arc = 70,
  glow = true,
  className = "",
}: BorderBeamProps) {
  const prefersReducedMotion = useReducedMotion();
  const angle = useMotionValue(0);

  useAnimationFrame((elapsed) => {
    if (prefersReducedMotion) return;
    // `elapsed` is milliseconds since the component mounted, so the beam is
    // continuous across re-renders and never jumps when props change.
    angle.set((elapsed / (duration * 1000)) * 360);
  });

  // Transparent everywhere except the arc, which fades from the head colour
  // into the tail colour and then out: a streak rather than a hard wedge.
  const stops = `transparent 0deg, ${colorTo} ${arc * 0.45}deg, ${colorFrom} ${arc}deg, transparent ${arc + 0.5}deg`;
  const background = useMotionTemplate`conic-gradient(from ${angle}deg at 50% 50%, ${stops})`;

  const ring = {
    background,
    padding: borderWidth,
    // Keep only the padding-box ring: paint the whole box, then punch out
    // everything inside the content box.
    WebkitMask:
      "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
    WebkitMaskComposite: "xor",
    mask: "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
    maskComposite: "exclude",
  } as const;

  return (
    <div className={cn("relative isolate rounded-xl", className)}>
      {glow ? (
        <motion.div
          aria-hidden="true"
          className="pointer-events-none absolute inset-0 -z-10 rounded-[inherit] opacity-70 blur-md"
          style={ring}
        />
      ) : null}

      <motion.div
        aria-hidden="true"
        className="pointer-events-none absolute inset-0 z-10 rounded-[inherit]"
        style={ring}
      />

      {/* The resting border stays underneath, so the element still reads as a
          bordered card in the stretch the beam is not lighting. */}
      <div className="relative h-full rounded-[inherit] border bg-card">
        {children}
      </div>
    </div>
  );
}

export default BorderBeam;
```

## Credits

Written by [Claude Code](https://claude.com/claude-code).
