Tech Store
← UI Components

Shine Button

Free

A call-to-action button with a soft light sweep on hover and keyboard focus.

0 px
100%

Code

ShineButton.tsx
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from "react";

type ShineButtonProps =
  | ({ href: string; children: ReactNode } & AnchorHTMLAttributes<HTMLAnchorElement>)
  | ({ href?: undefined; children: ReactNode } & ButtonHTMLAttributes<HTMLButtonElement>);

const base =
  "group relative inline-flex items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-violet-600 via-blue-600 to-teal-600 px-7 py-3 text-sm font-semibold text-white shadow-lg shadow-blue-600/25 transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500 active:translate-y-0 motion-reduce:transition-none motion-reduce:hover:translate-y-0";

function Shine() {
  return (
    <span
      aria-hidden="true"
      className="pointer-events-none absolute inset-y-0 -left-1/2 w-1/3 -skew-x-12 bg-white/40 blur-sm transition-transform duration-700 ease-out group-hover:translate-x-[400%] group-focus-visible:translate-x-[400%] motion-reduce:hidden"
    />
  );
}

export default function ShineButton(props: ShineButtonProps) {
  if (props.href !== undefined) {
    const { children, className = "", ...rest } = props;
    return (
      <a className={`${base} ${className}`} {...rest}>
        <Shine />
        <span className="relative">{children}</span>
      </a>
    );
  }
  const { children, className = "", type = "button", ...rest } = props;
  return (
    <button type={type} className={`${base} ${className}`} {...rest}>
      <Shine />
      <span className="relative">{children}</span>
    </button>
  );
}

export function Preview() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <ShineButton>Get started</ShineButton>
      <ShineButton href="#pricing">See pricing</ShineButton>
    </div>
  );
}

About Shine Button

A primary button for the one action that matters on a screen. The light sweep draws the eye without moving anything else on the page.

Features

  • A diagonal highlight sweeps across the gradient on hover and on keyboard focus.
  • Pure CSS animation on transform, so it stays on the GPU and never shifts layout.
  • Works as a <button> or, with href, as a link.

Accessibility

  • Native <button> / <a> semantics; no ARIA needed.
  • Visible focus ring that also triggers the sweep, so keyboard users get the same feedback.
  • The sweep is turned off under prefers-reduced-motion: reduce.

More components

View all →