Rainbow Button
A button whose top-lit depth border is walked by a band of rainbow light that lifts into the label on hover.
Installation
Complete the shared Setup first, then copy the component into
components/ui/rainbow-button.tsx.
'use client';import * as React from 'react';import { cn } from '@/lib/cn';const FOCUS = 'focus-visible:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:[outline-color:var(--rainbow-focus)]';export type RainbowButtonProps = { borderWidth?: number; speed?: number; accent?: string;} & React.ButtonHTMLAttributes<HTMLButtonElement>;export function RainbowButton({ borderWidth = 2, speed = 4, accent = '#22d3ee', children = 'Get Unlimited Access', className, style, ...props}: RainbowButtonProps) { const vars = { '--rainbow-focus': accent, '--rainbow-bw': `${borderWidth}px`, '--rainbow-speed': `${speed}s`, } as React.CSSProperties; return ( <button type="button" style={{ ...vars, ...style }} className={cn( 'bloom-edge rainbow-btn group relative isolate cursor-pointer select-none rounded-[14px]', 'shadow-[0_1px_2px_0_rgba(0,0,0,0.05),0_5px_14px_-10px_rgba(0,0,0,0.1)]', 'dark:shadow-[0_1px_2px_0_rgba(0,0,0,0.4),0_6px_16px_-10px_rgba(0,0,0,0.45)]', 'transition-transform duration-100 ease-out active:scale-[0.98] motion-reduce:active:scale-100', FOCUS, className, )} {...props} > <span aria-hidden className={cn( 'rainbow-ring pointer-events-none absolute inset-0 rounded-[inherit] [padding:var(--rainbow-bw)]', '[-webkit-mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)]', '[-webkit-mask-composite:xor] [mask-composite:exclude]', )} /> <span className={cn( 'relative flex items-center justify-center px-6 py-3', 'text-[15px] font-medium tracking-[-0.01em]', 'bg-gradient-to-b from-white to-[#f4f4f5] text-neutral-900', 'dark:from-[#1d1d1d] dark:to-[#141414] dark:text-white', 'shadow-[inset_0_1px_0_0_rgba(255,255,255,0.06)]', '[margin:var(--rainbow-bw)] [border-radius:calc(14px-var(--rainbow-bw))]', )} > <span className="rainbow-label">{children}</span> </span> </button> );}Add the animations to your global.css.
@property --rb-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes rainbow-walk {
to {
--rb-angle: 360deg;
}
}
@keyframes rainbow-hue {
to {
filter: hue-rotate(360deg);
}
}
@keyframes rainbow-text-walk {
to {
background-position: 200% 50%;
}
}
.rainbow-ring {
background: conic-gradient(
from var(--rb-angle),
transparent 0deg,
transparent 210deg,
#ff3b5c 250deg,
#ff8a3d 270deg,
#ffe14d 290deg,
#4ade80 310deg,
#22d3ee 330deg,
#b06bff 350deg,
transparent 360deg
);
animation:
rainbow-walk var(--rainbow-speed, 4s) linear infinite,
rainbow-hue calc(var(--rainbow-speed, 4s) * 2.5) linear infinite;
transition:
opacity 160ms cubic-bezier(0.23, 1, 0.32, 1),
transform 160ms cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) {
.rainbow-btn:hover .rainbow-ring {
opacity: 0;
transform: scale(1.035);
transition-duration: 280ms;
}
.rainbow-btn:hover .rainbow-label {
background-image: linear-gradient(
90deg,
#ff3b5c,
#ff8a3d,
#ffe14d,
#4ade80,
#22d3ee,
#5b8cff,
#b06bff,
#ff3b5c
);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
animation: rainbow-text-walk var(--rainbow-speed, 4s) linear infinite;
}
}
@media (prefers-reduced-motion: reduce) {
.rainbow-ring,
.rainbow-btn:hover .rainbow-label {
animation: none;
}
}Usage
import { RainbowButton } from '@/components/ui/rainbow-button';
export default function Example() {
return <RainbowButton>Get Unlimited Access</RainbowButton>;
}Examples
Tune the lap speed, band thickness, and focus accent.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
speed | number | 4 | Seconds for one full lap of the band. |
borderWidth | number | 2 | Thickness of the walking band, in px. |
accent | string | "#22d3ee" | Focus-outline color. |
className | string | - | Forwarded to the button. |
The button also accepts every native <button> prop.