Shine Border
Effects & Borders

Shine Border

A soft highlight that glides around the border edge like light catching a bevel, over a faint static rim.

Install

npx shadcn@latest add @paragon/shine-border

shine-border.tsx

"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

export interface ShineBorderProps extends React.ComponentProps<"div"> {
  /** Seconds per lap around the border. */
  duration?: number;
  /** Border ring thickness in px. */
  borderWidth?: number;
  /** Corner radius of the ring in px. Match the parent's radius. */
  borderRadius?: number;
  /** Overall shine opacity, 0–1. */
  strength?: number;
  /** Angular width of the bright arc, in degrees (10–180). */
  spread?: number;
  /**
   * Shine color. Defaults to the theme foreground so it reads as a soft
   * specular in both themes; pass "white" over brand/colored surfaces.
   */
  color?: string;
  /** Faint always-on base rim beneath the moving shine, 0–1. */
  rim?: number;
}

const clamp = (v: number, lo: number, hi: number) =>
  Math.min(Math.max(v, lo), hi);

/**
 * A soft highlight that travels around the border edge, like light catching a
 * bevel — quieter and rounder than border-beam's comet.
 *
 * Technique (border-beam engine idiom): a flat tinted ring is confined to the
 * border via the double-mask (padding + content-box/exclude); a second conic
 * mask — a single soft arc driven by one registered @property angle — rides
 * over it, so only that stretch of the rim lights up and glides around the
 * perimeter for the cost of one interpolated value. A faint static rim keeps
 * the edge defined between passes. Pauses offscreen; reduced motion parks the
 * arc. Absolutely positioned — parent needs position: relative.
 */
export function ShineBorder({
  duration = 4,
  borderWidth = 1.5,
  borderRadius = 12,
  strength = 1,
  spread = 70,
  color = "var(--color-foreground)",
  rim = 0.12,
  className,
  style,
  ref: forwardedRef,
  ...props
}: ShineBorderProps) {
  const id = React.useId().replace(/[^a-zA-Z0-9-]/g, "");
  const localRef = React.useRef<HTMLDivElement | null>(null);
  const [inView, setInView] = React.useState(true);

  React.useEffect(() => {
    const node = localRef.current;
    if (!node) return;
    const observer = new IntersectionObserver(([entry]) => {
      setInView(entry?.isIntersecting ?? true);
    });
    observer.observe(node);
    return () => observer.disconnect();
  }, []);

  const level = clamp(strength, 0, 1);
  const rimLevel = clamp(rim, 0, 1);
  const arc = clamp(spread, 10, 180);
  // Half-arc on each side of the head; a soft ramp in, bright crest, ramp out.
  const half = arc / 2;
  const q1 = (half * 0.55).toFixed(1);
  const tint = (pct: number) =>
    `color-mix(in oklab, ${color} ${pct}%, transparent)`;

  const ringMask =
    "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)";

  return (
    <>
      <style href={`paragon-shine-border-${id}`} precedence="paragon">{`
        @property --shine-b-${id} {
          syntax: "<angle>";
          initial-value: 0deg;
          inherits: true;
        }
        @keyframes shine-border-${id} {
          to { --shine-b-${id}: 360deg; }
        }
        @media (prefers-reduced-motion: reduce) {
          [data-shineb="${id}"] { animation: none !important; }
        }
      `}</style>
      <div
        aria-hidden
        data-shineb={id}
        ref={(node) => {
          localRef.current = node;
          if (typeof forwardedRef === "function") forwardedRef(node);
          else if (forwardedRef) forwardedRef.current = node;
        }}
        className={cn("pointer-events-none absolute inset-0", className)}
        style={{
          borderRadius,
          opacity: level,
          animation: `shine-border-${id} ${duration}s linear infinite`,
          animationPlayState: inView ? "running" : "paused",
          ...style,
        }}
        {...props}
      >
        {/* Static base rim — keeps the edge defined between passes. */}
        {rimLevel > 0 && (
          <div
            style={{
              position: "absolute",
              inset: 0,
              borderRadius,
              padding: borderWidth,
              background: tint(100 * rimLevel * 0.6),
              mask: ringMask,
              WebkitMaskComposite: "xor",
              maskComposite: "exclude",
            }}
          />
        )}
        {/* Moving shine: tinted ring intersected with a soft conic arc. */}
        <div
          style={
            {
              position: "absolute",
              inset: 0,
              borderRadius,
              padding: borderWidth,
              background: tint(85),
              mask: `conic-gradient(from calc(var(--shine-b-${id}) - ${half}deg), transparent 0deg, ${tint(40)} ${q1}deg, #fff ${half}deg, ${tint(40)} ${(arc - Number(q1)).toFixed(1)}deg, transparent ${arc}deg), linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)`,
              WebkitMaskComposite: "source-in, xor",
              maskComposite: "intersect, exclude",
            } as React.CSSProperties
          }
        />
      </div>
    </>
  );
}