A beam of light traveling the border, shifting color as it moves. Pure CSS engine — a rotating conic mask over stationary gradients.
npx shadcn@latest add @paragon/border-beam"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
export interface BorderBeamProps extends React.ComponentProps<"div"> {
/** Seconds per revolution. */
duration?: number;
/** Border ring thickness in px. */
borderWidth?: number;
/** Overall effect opacity, 0–1. */
strength?: number;
/** Corner radius of the ring in px. Match the parent's radius. */
borderRadius?: number;
/** Beam colors, painted as stationary glows the beam travels across. */
colors?: string[];
}
/**
* A beam of light traveling the border of the parent element.
*
* Technique: stationary multi-hue radial gradients are parked around the
* perimeter; only a conic-gradient mask window (a registered @property angle)
* rotates over them, so the beam shifts color as it travels for the cost of
* one interpolated value. Confined to the border via the double-mask ring
* trick. Absolutely positioned — parent needs position: relative.
*/
export function BorderBeam({
duration = 6,
borderWidth = 1,
strength = 1,
borderRadius = 12,
colors = ["#f43f5e", "#3b82f6", "#22c55e", "#a855f7", "#f97316"],
className,
style,
...props
}: BorderBeamProps) {
const id = React.useId().replace(/[^a-zA-Z0-9-]/g, "");
const positions = [
"20% 0%",
"100% 30%",
"80% 100%",
"0% 70%",
"55% 100%",
];
const field = colors
.map(
(color, i) =>
`radial-gradient(ellipse 120px 60px at ${positions[i % positions.length]}, ${color}, transparent)`,
)
.join(",");
return (
<>
<style href={`paragon-border-beam-${id}`} precedence="paragon">{`
@property --beam-angle-${id} {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
@keyframes beam-spin-${id} {
0% { --beam-angle-${id}: 0deg; }
50% { --beam-angle-${id}: 180deg; }
100% { --beam-angle-${id}: 360deg; }
}
@media (prefers-reduced-motion: reduce) {
[data-beam="${id}"] { animation: none !important; }
}
`}</style>
<div
aria-hidden
data-beam={id}
className={cn("pointer-events-none absolute inset-0", className)}
style={
{
borderRadius,
padding: borderWidth,
background: field,
opacity: 0.9 * strength,
mask: `conic-gradient(from var(--beam-angle-${id}), transparent 0%, transparent 55%, rgba(255,255,255,0.4) 70%, white 80%, white 92%, transparent 98%), linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)`,
WebkitMaskComposite: "source-in, xor",
maskComposite: "intersect, exclude",
animation: `beam-spin-${id} ${duration}s linear infinite`,
...style,
} as React.CSSProperties
}
{...props}
/>
</>
);
}