All components

Moving Border

A light laps the button's border via offset-path.

0.9 KB gzippedZero dependenciesReduced-motion safeBase UI & Radix compatible

Installation

npx shadcn@latest add https://innsite.com.mx/r/moving-border.json

Source

import { cn } from "@/lib/utils";

interface MovingBorderProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  /** Seconds for one lap of the border */
  duration?: number;
  /** Length of the travelling light, in px */
  size?: number;
  /** Corner radius of the border path, in px */
  radius?: number;
  children: React.ReactNode;
}

/**
 * Button with a light that laps its border, driven by `offset-path`.
 * Zero dependencies — one keyframe does the whole thing.
 */
export function MovingBorder({
  duration = 4,
  size = 90,
  radius = 12,
  children,
  className,
  style,
  ...props
}: MovingBorderProps) {
  return (
    <button
      {...props}
      data-slot="moving-border"
      style={
        {
          "--mb-duration": `${duration}s`,
          "--mb-size": `${size}px`,
          "--mb-radius": `${radius}px`,
          borderRadius: radius,
          ...style,
        } as React.CSSProperties
      }
      className={cn(
        "relative inline-flex h-11 cursor-pointer items-center justify-center overflow-hidden bg-background p-px text-sm font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50",
        className
      )}
    >
      <span
        aria-hidden
        className="pointer-events-none absolute inset-0 motion-reduce:hidden"
      >
        <span
          className="absolute aspect-square w-(--mb-size) animate-moving-border bg-[radial-gradient(circle,var(--brand-via),transparent_65%)]"
          style={{
            offsetPath: `rect(0 auto auto 0 round var(--mb-radius))`,
          }}
        />
      </span>
      <span className="relative z-10 flex h-full w-full items-center justify-center rounded-[inherit] border bg-background px-6">
        {children}
      </span>
    </button>
  );
}