Glow Ring Button
Original · freeButtons with a pulsing glow ring on hover.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/btn-glow-ring.json"use client";
import { useState, useRef } from "react";
import type { ReactNode, ButtonHTMLAttributes } from "react";
import { useReducedMotion } from "motion/react";
type Accent = "indigo" | "violet" | "emerald" | "rose" | "amber" | "sky";
const ACCENTS: Record<
Accent,
{
ring: string;
face: string;
faceHover: string;
text: string;
glow: string;
softFace: string;
softText: string;
softBorder: string;
}
> = {
indigo: {
ring: "bgr-ring-indigo",
face: "bg-indigo-600",
faceHover: "group-hover:bg-indigo-500",
text: "text-white",
glow: "shadow-indigo-500/50",
softFace: "bg-indigo-50 dark:bg-indigo-500/10",
softText: "text-indigo-700 dark:text-indigo-300",
softBorder: "border-indigo-200 dark:border-indigo-500/30",
},
violet: {
ring: "bgr-ring-violet",
face: "bg-violet-600",
faceHover: "group-hover:bg-violet-500",
text: "text-white",
glow: "shadow-violet-500/50",
softFace: "bg-violet-50 dark:bg-violet-500/10",
softText: "text-violet-700 dark:text-violet-300",
softBorder: "border-violet-200 dark:border-violet-500/30",
},
emerald: {
ring: "bgr-ring-emerald",
face: "bg-emerald-600",
faceHover: "group-hover:bg-emerald-500",
text: "text-white",
glow: "shadow-emerald-500/50",
softFace: "bg-emerald-50 dark:bg-emerald-500/10",
softText: "text-emerald-700 dark:text-emerald-300",
softBorder: "border-emerald-200 dark:border-emerald-500/30",
},
rose: {
ring: "bgr-ring-rose",
face: "bg-rose-600",
faceHover: "group-hover:bg-rose-500",
text: "text-white",
glow: "shadow-rose-500/50",
softFace: "bg-rose-50 dark:bg-rose-500/10",
softText: "text-rose-700 dark:text-rose-300",
softBorder: "border-rose-200 dark:border-rose-500/30",
},
amber: {
ring: "bgr-ring-amber",
face: "bg-amber-500",
faceHover: "group-hover:bg-amber-400",
text: "text-amber-950",
glow: "shadow-amber-400/50",
softFace: "bg-amber-50 dark:bg-amber-500/10",
softText: "text-amber-700 dark:text-amber-300",
softBorder: "border-amber-200 dark:border-amber-500/30",
},
sky: {
ring: "bgr-ring-sky",
face: "bg-sky-600",
faceHover: "group-hover:bg-sky-500",
text: "text-white",
glow: "shadow-sky-500/50",
softFace: "bg-sky-50 dark:bg-sky-500/10",
softText: "text-sky-700 dark:text-sky-300",
softBorder: "border-sky-200 dark:border-sky-500/30",
},
};
type Size = "sm" | "md" | "lg";
const SIZES: Record<Size, string> = {
sm: "h-9 px-4 text-sm gap-1.5",
md: "h-11 px-6 text-sm gap-2",
lg: "h-14 px-8 text-base gap-2.5",
};
const FOCUS =
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-slate-900 dark:focus-visible:ring-slate-100 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950";
function GlowButton({
children,
accent = "indigo",
size = "md",
disabled = false,
loading = false,
className = "",
...rest
}: {
children: ReactNode;
accent?: Accent;
size?: Size;
disabled?: boolean;
loading?: boolean;
className?: string;
} & ButtonHTMLAttributes<HTMLButtonElement>) {
const a = ACCENTS[accent];
const isOff = disabled || loading;
return (
<button
type="button"
disabled={isOff}
aria-busy={loading || undefined}
className={`group relative inline-flex select-none items-center justify-center rounded-full font-semibold ${SIZES[size]} ${a.face} ${a.text} ${FOCUS} transition-[transform,background-color,box-shadow] duration-200 active:scale-[0.97] disabled:pointer-events-none disabled:opacity-50 ${
isOff ? "" : `${a.faceHover} hover:shadow-lg hover:${a.glow}`
} ${className}`}
{...rest}
>
{!isOff && (
<span
aria-hidden="true"
className={`pointer-events-none absolute -inset-px rounded-full opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100 ${a.ring}`}
/>
)}
<span className="relative z-10 inline-flex items-center justify-center gap-[inherit]">
{loading && (
<svg
className="bgr-spin h-4 w-4"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
>
<circle
cx="12"
cy="12"
r="9"
stroke="currentColor"
strokeWidth="3"
className="opacity-25"
/>
<path
d="M21 12a9 9 0 0 0-9-9"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
/>
</svg>
)}
{children}
</span>
</button>
);
}
function ArrowIcon() {
return (
<svg
className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
>
<path
d="M5 12h14M13 6l6 6-6 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export default function BtnGlowRing() {
const reduce = useReducedMotion();
const [liked, setLiked] = useState(false);
const [likes, setLikes] = useState(248);
const [subscribed, setSubscribed] = useState(false);
const [copied, setCopied] = useState(false);
const [loading, setLoading] = useState(false);
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
function toggleLike() {
setLiked((v) => {
setLikes((n) => (v ? n - 1 : n + 1));
return !v;
});
}
async function copy() {
try {
await navigator.clipboard.writeText("npm i motion");
} catch {
/* clipboard unavailable */
}
setCopied(true);
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => setCopied(false), 1600);
}
function fakeDeploy() {
setLoading(true);
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => setLoading(false), 1800);
}
const glowFilter = reduce ? "none" : undefined;
return (
<section className="relative w-full bg-slate-50 px-6 py-20 text-slate-900 dark:bg-slate-950 dark:text-slate-100 sm:px-8 sm:py-28">
<style>{`
@keyframes bgr-pulse {
0%, 100% { transform: scale(1); opacity: 0.55; }
50% { transform: scale(1.06); opacity: 1; }
}
@keyframes bgr-spin {
to { transform: rotate(360deg); }
}
.bgr-spin { animation: bgr-spin 0.8s linear infinite; }
.bgr-ring-indigo,
.bgr-ring-violet,
.bgr-ring-emerald,
.bgr-ring-rose,
.bgr-ring-amber,
.bgr-ring-sky {
animation: bgr-pulse 1.6s ease-in-out infinite;
}
.bgr-ring-indigo { box-shadow: 0 0 0 2px rgba(99,102,241,0.9), 0 0 22px 4px rgba(99,102,241,0.55); }
.bgr-ring-violet { box-shadow: 0 0 0 2px rgba(139,92,246,0.9), 0 0 22px 4px rgba(139,92,246,0.55); }
.bgr-ring-emerald { box-shadow: 0 0 0 2px rgba(16,185,129,0.9), 0 0 22px 4px rgba(16,185,129,0.55); }
.bgr-ring-rose { box-shadow: 0 0 0 2px rgba(244,63,94,0.9), 0 0 22px 4px rgba(244,63,94,0.55); }
.bgr-ring-amber { box-shadow: 0 0 0 2px rgba(245,158,11,0.9), 0 0 22px 4px rgba(245,158,11,0.55); }
.bgr-ring-sky { box-shadow: 0 0 0 2px rgba(14,165,233,0.9), 0 0 22px 4px rgba(14,165,233,0.55); }
@media (prefers-reduced-motion: reduce) {
.bgr-spin,
.bgr-ring-indigo,
.bgr-ring-violet,
.bgr-ring-emerald,
.bgr-ring-rose,
.bgr-ring-amber,
.bgr-ring-sky { animation: none !important; }
}
`}</style>
<div className="mx-auto max-w-3xl">
<header className="mb-14 text-center">
<p className="mb-3 text-xs font-semibold uppercase tracking-[0.2em] text-indigo-600 dark:text-indigo-400">
FreeCode / Buttons
</p>
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">
Glow Ring Buttons
</h2>
<p className="mx-auto mt-4 max-w-xl text-sm text-slate-600 dark:text-slate-400">
Hover or focus any button for a pulsing halo. Every control is a real{" "}
<code className="rounded bg-slate-200 px-1.5 py-0.5 text-xs dark:bg-slate-800">
<button>
</code>{" "}
with keyboard focus rings and live state.
</p>
</header>
{/* Accent palette */}
<div className="mb-12">
<h3 className="mb-5 text-sm font-medium text-slate-500 dark:text-slate-400">
Accents
</h3>
<div className="flex flex-wrap items-center justify-center gap-4">
<GlowButton accent="indigo">Get started</GlowButton>
<GlowButton accent="violet">Upgrade</GlowButton>
<GlowButton accent="emerald">Publish</GlowButton>
<GlowButton accent="sky">Connect</GlowButton>
<GlowButton accent="amber">Boost</GlowButton>
<GlowButton accent="rose">Go live</GlowButton>
</div>
</div>
{/* Sizes */}
<div className="mb-12">
<h3 className="mb-5 text-sm font-medium text-slate-500 dark:text-slate-400">
Sizes
</h3>
<div className="flex flex-wrap items-center justify-center gap-4">
<GlowButton accent="indigo" size="sm">
Small
</GlowButton>
<GlowButton accent="indigo" size="md">
Medium
</GlowButton>
<GlowButton accent="indigo" size="lg">
Large
<ArrowIcon />
</GlowButton>
</div>
</div>
{/* Live interactive row */}
<div className="mb-12">
<h3 className="mb-5 text-sm font-medium text-slate-500 dark:text-slate-400">
Live state
</h3>
<div className="flex flex-wrap items-center justify-center gap-4">
{/* Like toggle */}
<button
type="button"
onClick={toggleLike}
aria-pressed={liked}
aria-label={liked ? "Unlike" : "Like"}
className={`group relative inline-flex h-11 select-none items-center gap-2 rounded-full border px-5 text-sm font-semibold transition-[transform,background-color,box-shadow] duration-200 active:scale-[0.97] ${FOCUS} ${
liked
? "border-rose-300 bg-rose-50 text-rose-700 hover:shadow-lg hover:shadow-rose-500/40 dark:border-rose-500/40 dark:bg-rose-500/10 dark:text-rose-300"
: "border-slate-300 bg-white text-slate-700 hover:shadow-lg hover:shadow-rose-500/30 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200"
}`}
>
<span
aria-hidden="true"
className="bgr-ring-rose pointer-events-none absolute -inset-px rounded-full opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100"
/>
<span className="relative z-10 inline-flex items-center gap-2">
<svg
className="h-4 w-4"
viewBox="0 0 24 24"
fill={liked ? "currentColor" : "none"}
stroke="currentColor"
strokeWidth="2"
aria-hidden="true"
>
<path d="M12 21s-6.5-4.35-9.33-8.05C.9 10.2 1.36 6.9 3.7 5.3c1.9-1.3 4.4-.8 5.8.9L12 9l2.5-2.8c1.4-1.7 3.9-2.2 5.8-.9 2.34 1.6 2.8 4.9.9 7.65C18.5 16.65 12 21 12 21z" />
</svg>
<span className="tabular-nums">{likes}</span>
</span>
</button>
{/* Subscribe toggle */}
<button
type="button"
onClick={() => setSubscribed((v) => !v)}
aria-pressed={subscribed}
className={`group relative inline-flex h-11 select-none items-center gap-2 rounded-full px-6 text-sm font-semibold transition-[transform,background-color,box-shadow] duration-200 active:scale-[0.97] ${FOCUS} ${
subscribed
? "bg-slate-200 text-slate-700 hover:bg-slate-300 dark:bg-slate-800 dark:text-slate-200 dark:hover:bg-slate-700"
: "bg-emerald-600 text-white hover:bg-emerald-500 hover:shadow-lg hover:shadow-emerald-500/50"
}`}
>
{!subscribed && (
<span
aria-hidden="true"
className="bgr-ring-emerald pointer-events-none absolute -inset-px rounded-full opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100"
/>
)}
<span className="relative z-10 inline-flex items-center gap-2">
{subscribed ? (
<svg
className="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M20 6 9 17l-5-5" />
</svg>
) : null}
{subscribed ? "Subscribed" : "Subscribe"}
</span>
</button>
{/* Copy */}
<button
type="button"
onClick={copy}
aria-label="Copy install command"
className={`group relative inline-flex h-11 select-none items-center gap-2 rounded-full border border-slate-300 bg-white px-5 font-mono text-sm text-slate-700 transition-[transform,background-color,box-shadow] duration-200 active:scale-[0.97] hover:shadow-lg hover:shadow-sky-500/30 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 ${FOCUS}`}
>
<span
aria-hidden="true"
className="bgr-ring-sky pointer-events-none absolute -inset-px rounded-full opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100"
/>
<span className="relative z-10 inline-flex items-center gap-2">
{copied ? (
<svg
className="h-4 w-4 text-emerald-500"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M20 6 9 17l-5-5" />
</svg>
) : (
<svg
className="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
aria-hidden="true"
>
<rect x="9" y="9" width="11" height="11" rx="2" />
<path d="M5 15V5a2 2 0 0 1 2-2h10" />
</svg>
)}
{copied ? "Copied!" : "npm i motion"}
</span>
</button>
</div>
</div>
{/* States */}
<div>
<h3 className="mb-5 text-sm font-medium text-slate-500 dark:text-slate-400">
Loading & disabled
</h3>
<div className="flex flex-wrap items-center justify-center gap-4">
<GlowButton accent="violet" loading={loading} onClick={fakeDeploy}>
{loading ? "Deploying" : "Deploy to prod"}
</GlowButton>
<GlowButton accent="indigo" disabled>
Disabled
</GlowButton>
{/* Icon-only */}
<button
type="button"
aria-label="Add to favorites"
style={{ filter: glowFilter }}
className={`group relative inline-flex h-11 w-11 select-none items-center justify-center rounded-full bg-amber-500 text-amber-950 transition-[transform,background-color,box-shadow] duration-200 active:scale-[0.97] hover:bg-amber-400 hover:shadow-lg hover:shadow-amber-400/50 ${FOCUS}`}
>
<span
aria-hidden="true"
className="bgr-ring-amber pointer-events-none absolute -inset-px rounded-full opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100"
/>
<svg
className="relative z-10 h-5 w-5"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
>
<path d="M12 2.5l2.9 5.9 6.5.95-4.7 4.58 1.11 6.47L12 17.9l-5.81 3.07 1.11-6.47-4.7-4.58 6.5-.95z" />
</svg>
</button>
</div>
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Shimmer Button
MITA dark pill button with a continuous conic-gradient light shimmer sweeping around its border.

Rainbow Gradient Button
MITA button wrapped in an animated multi-colour gradient border with a matching blurred underglow.

Interactive Hover Button
MITA pill button whose dot expands to fill the surface and reveals an arrow label on hover.

Pulsating Button
MITA solid button that emits a soft, continuously pulsing glow ring to draw the eye to the primary action.

Shiny Button
MITA frosted-glass button with a spring-animated light sweeping across its text and border.

Solid Set Button
OriginalA set of solid buttons: primary, secondary, success and danger, in three sizes.

Outline Set Button
OriginalOutline button variants across colours and sizes with a hover fill.

Ghost Set Button
OriginalGhost and text buttons with subtle hover backgrounds.

Soft Set Button
OriginalSoft, tinted buttons across semantic colours.

Gradient Set Button
OriginalGradient-filled buttons with a hover shift.
Icon Leading Button
OriginalButtons with leading and trailing icons.
Icon Only Button
OriginalSquare and circular icon-only buttons with tooltips and aria-labels.

