Cards
BorderGlow
The edge nearest the pointer lights up, and starts glowing before the pointer even reaches the card.
Download
- React
- Tailwind CSS v4
components/ui/border-glow.tsx
"use client";
import { useEffect, useRef, type CSSProperties, type ReactNode } from "react";
type BorderGlowProps = {
children: ReactNode;
className?: string;
/** Glow colour. Any CSS colour. */
color?: string;
/** Distance from the card, in pixels, where the glow starts to show. */
reach?: number;
/** Width of the lit part of the border, in degrees. */
spread?: number;
/** Border colour where the light is not. */
border?: string;
};
/* BorderGlow: the edge nearest the pointer lights up, even before the pointer reaches the card. */
export function BorderGlow({
children,
className = "rounded-[15px] bg-[#100e1c] p-6 text-white",
color = "#a78bfa",
reach = 160,
spread = 70,
border = "rgba(255,255,255,0.1)",
}: BorderGlowProps) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let frame = 0;
const onMove = (e: PointerEvent) => {
cancelAnimationFrame(frame);
frame = requestAnimationFrame(() => {
const el = ref.current;
if (!el) return;
const r = el.getBoundingClientRect();
const angle = (Math.atan2(e.clientY - (r.top + r.height / 2), e.clientX - (r.left + r.width / 2)) * 180) / Math.PI + 90;
const dx = Math.max(r.left - e.clientX, 0, e.clientX - r.right);
const dy = Math.max(r.top - e.clientY, 0, e.clientY - r.bottom);
const glow = Math.max(0, 1 - Math.hypot(dx, dy) / reach);
el.style.setProperty("--angle", `${angle}deg`);
el.style.setProperty("--glow", `${glow}`);
});
};
window.addEventListener("pointermove", onMove, { passive: true });
return () => {
window.removeEventListener("pointermove", onMove);
cancelAnimationFrame(frame);
};
}, [reach]);
const light = `conic-gradient(from calc(var(--angle) - ${spread}deg), transparent 0deg, ${color} ${spread}deg, transparent ${spread * 2}deg)`;
return (
<div
ref={ref}
className="relative rounded-2xl p-px"
style={{ "--angle": "0deg", "--glow": 0, background: border } as CSSProperties}
>
<span
aria-hidden
className="pointer-events-none absolute inset-0 rounded-[inherit] blur-md"
style={{ background: light, opacity: "calc(var(--glow) * 0.6)" }}
/>
<span
aria-hidden
className="pointer-events-none absolute inset-0 rounded-[inherit]"
style={{ background: light, opacity: "var(--glow)" }}
/>
<div className={`relative h-full ${className}`}>{children}</div>
</div>
);
}
1. Paste the Code tab into
components/ui/border-glow.tsx. The styles are Tailwind CSS v4 classes.2. Use it. This is the preview above:
app/border-glow-demo.tsx
import { BorderGlow } from "@/components/ui/border-glow"; export default function BorderGlowExample() { return ( <div className="grid h-full place-items-center bg-[#0b0a14] p-6"> <div className="w-full max-w-[300px]"> <BorderGlow> <p className="font-mono text-[11px] uppercase tracking-[0.2em] text-[#a78bfa]">Deploys</p> <p className="mt-3 text-4xl font-semibold">248</p> <p className="mt-2 text-sm text-white/60">Bring your pointer near the card.</p> </BorderGlow> </div> </div> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| children | ReactNode | — | Card content. |
| color | string | "#a78bfa" | Glow colour. |
| reach | number | 160 | Distance from the card, in pixels, where the glow starts. |
| spread | number | 70 | Width of the lit part of the border, in degrees. |
| border | string | "rgba(255,255,255,0.1)" | Border colour where the light is not. |
| className | string | dark inner card | Look of the inside. |