Chrome
FreeBorder Beam
Marks the one surface that matters on a page: a plan, a prompt box, a live session. The beam follows the parent's own radius, so it fits cards, inputs and full panels without extra markup.
Install
No key needed.
npx @galactrex/ui add border-beamUsage
import { BorderBeam } from "@/components/galactrex/border-beam";
<div className="relative rounded-xl border border-gxui-line p-6">
<BorderBeam />
Pro plan
</div>Source
Installs to components/galactrex/border-beam.tsx, with the shared motion primitives beside it.
"use client";
import { useRef, type CSSProperties } from "react";
import { motion } from "motion/react";
import { cx } from "@/components/galactrex/motion/cx";
import { useElementSize } from "@/components/galactrex/motion/use-element-size";
import { useReducedMotion } from "@/components/galactrex/motion/use-reduced-motion";
import { useVisible } from "@/components/galactrex/motion/use-visible";
export type BorderBeamProps = {
/** seconds for one lap of the border */
duration?: number;
/** length of the beam as a share of the lap, 0.05 to 0.5 */
length?: number;
/** border width in px */
width?: number;
/** any CSS colour for a single-colour beam; by default the tail runs through the theme spectrum */
color?: string;
/** run counter-clockwise */
reverse?: boolean;
/** seconds before the first lap */
delay?: number;
className?: string;
};
/** A light that travels the border of its parent. Place it inside any element with position: relative and a
border radius; it inherits the radius and never takes pointer events. The beam is a rotating conic gradient
seen through a ring mask, so each frame is a compositor transform, not a repaint of the border. */
export function BorderBeam({ duration = 6, length = 0.18, width = 1, color, reverse = false, delay = 0, className }: BorderBeamProps) {
const ref = useRef<HTMLSpanElement>(null);
const { width: w, height: h } = useElementSize(ref);
const reduced = useReducedMotion();
const visible = useVisible(ref);
const side = Math.ceil(Math.hypot(w, h)) + 2;
const share = Math.min(0.5, Math.max(0.05, length)) * 360;
const ring: CSSProperties = {
padding: width,
WebkitMask: "linear-gradient(black, black) content-box exclude, linear-gradient(black, black)",
mask: "linear-gradient(black, black) content-box exclude, linear-gradient(black, black)",
};
const beam: CSSProperties = {
width: side,
height: side,
marginLeft: -side / 2,
marginTop: -side / 2,
// the head is the brightest point; without a colour the tail cools through the spectrum behind it
background: color
? `conic-gradient(from 0deg, transparent 0deg, transparent ${360 - share}deg, ${color} 360deg)`
: `conic-gradient(from 0deg, transparent 0deg, transparent ${360 - share}deg, var(--gxui-spectrum-1) ${360 - share * 0.66}deg, var(--gxui-spectrum-2) ${360 - share * 0.33}deg, var(--gxui-spectrum-3) ${360 - share * 0.08}deg, var(--gxui-glow) 360deg)`,
};
return (
<span ref={ref} aria-hidden className={cx("pointer-events-none absolute inset-0 overflow-hidden rounded-[inherit]", className)} style={ring}>
{w > 0 && (
<motion.span
className="absolute left-1/2 top-1/2 block will-change-transform"
style={beam}
initial={{ rotate: reverse ? 360 : 0 }}
animate={reduced || !visible ? undefined : { rotate: reverse ? 0 : 360 }}
transition={{ duration, delay, ease: "linear", repeat: Infinity }}
/>
)}
</span>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
duration | number | 6 | Seconds for one lap. |
length | number | 0.18 | Beam length as a share of the lap, 0.05 to 0.5. |
width | number | 1 | Border width in px. |
color | string | var(--gxui-glow) | Any CSS colour. |
reverse | boolean | false | Run counter-clockwise. |
delay | number | 0 | Seconds before the first lap. |
className | string | — | Extra classes for the outer element. |
Variants
- Single beam. The default: one light, one lap every six seconds.
- Paired. Two beams with opposite `reverse` and half-lap `delay` for a balanced trace.
Accessibility
- Decorative only: aria-hidden and pointer-events: none, so it never intercepts clicks or reaches assistive tech.
Motion and reduced motion
- Rotation by transform on a single layer; no repaint of the border per frame.
- Pauses while offscreen.
- Reduced motion: the beam holds still at its starting point.