Social Set Button
Original · freeA set of social sign-in buttons with inline brand-style glyphs.
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-social-set.json"use client";
import { useState, type ReactNode } from "react";
import { useReducedMotion } from "motion/react";
type ProviderId =
| "google"
| "apple"
| "github"
| "microsoft"
| "facebook"
| "x";
type Provider = {
id: ProviderId;
label: string;
glyph: ReactNode;
/** brand-tinted background for the solid/pill variants (light + dark handled inline) */
solid: string;
ring: string;
};
const GoogleGlyph = (
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path
fill="#4285F4"
d="M23.52 12.27c0-.86-.08-1.68-.22-2.47H12v4.68h6.46a5.52 5.52 0 0 1-2.4 3.62v3h3.88c2.27-2.09 3.58-5.17 3.58-8.83Z"
/>
<path
fill="#34A853"
d="M12 24c3.24 0 5.96-1.08 7.94-2.9l-3.88-3c-1.08.72-2.45 1.15-4.06 1.15-3.12 0-5.77-2.11-6.71-4.95H1.29v3.1A12 12 0 0 0 12 24Z"
/>
<path
fill="#FBBC05"
d="M5.29 14.3a7.2 7.2 0 0 1 0-4.6v-3.1H1.29a12 12 0 0 0 0 10.8l4-3.1Z"
/>
<path
fill="#EA4335"
d="M12 4.75c1.76 0 3.34.61 4.59 1.8l3.43-3.43C17.95 1.19 15.24 0 12 0A12 12 0 0 0 1.29 6.6l4 3.1C6.23 6.86 8.88 4.75 12 4.75Z"
/>
</svg>
);
const AppleGlyph = (
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="currentColor">
<path d="M17.05 12.54c-.03-2.68 2.19-3.97 2.29-4.03-1.25-1.83-3.19-2.08-3.88-2.11-1.65-.17-3.22.97-4.06.97-.84 0-2.13-.95-3.5-.92-1.8.03-3.46 1.05-4.39 2.66-1.87 3.25-.48 8.06 1.34 10.7.89 1.29 1.95 2.74 3.34 2.69 1.34-.05 1.85-.87 3.47-.87 1.62 0 2.08.87 3.5.84 1.44-.03 2.36-1.32 3.24-2.62 1.02-1.5 1.44-2.95 1.46-3.03-.03-.01-2.8-1.08-2.83-4.25ZM14.5 4.68c.74-.9 1.24-2.15 1.1-3.4-1.07.04-2.36.71-3.12 1.61-.68.79-1.28 2.06-1.12 3.28 1.19.09 2.4-.61 3.14-1.49Z" />
</svg>
);
const GitHubGlyph = (
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="currentColor">
<path d="M12 .5A11.5 11.5 0 0 0 .5 12a11.5 11.5 0 0 0 7.86 10.92c.58.1.79-.25.79-.56v-2c-3.2.7-3.88-1.37-3.88-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.2 1.77 1.2 1.03 1.77 2.71 1.26 3.37.96.1-.75.4-1.26.73-1.55-2.56-.29-5.25-1.28-5.25-5.7 0-1.26.45-2.29 1.19-3.1-.12-.29-.52-1.47.11-3.06 0 0 .97-.31 3.18 1.18a11 11 0 0 1 5.79 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.24 2.77.12 3.06.74.81 1.19 1.84 1.19 3.1 0 4.43-2.7 5.41-5.27 5.69.41.36.78 1.06.78 2.14v3.17c0 .31.21.67.8.56A11.5 11.5 0 0 0 23.5 12 11.5 11.5 0 0 0 12 .5Z" />
</svg>
);
const MicrosoftGlyph = (
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="#F25022" d="M1 1h10.2v10.2H1z" />
<path fill="#7FBA00" d="M12.8 1H23v10.2H12.8z" />
<path fill="#00A4EF" d="M1 12.8h10.2V23H1z" />
<path fill="#FFB900" d="M12.8 12.8H23V23H12.8z" />
</svg>
);
const FacebookGlyph = (
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="currentColor">
<path d="M24 12.07C24 5.4 18.63 0 12 0S0 5.4 0 12.07C0 18.1 4.39 23.1 10.13 24v-8.44H7.08v-3.49h3.05V9.41c0-3.02 1.79-4.69 4.53-4.69 1.31 0 2.68.24 2.68.24v2.97h-1.51c-1.49 0-1.96.93-1.96 1.89v2.25h3.33l-.53 3.49h-2.8V24C19.61 23.1 24 18.1 24 12.07Z" />
</svg>
);
const XGlyph = (
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="currentColor">
<path d="M18.9 1.5h3.68l-8.04 9.19L24 22.5h-7.4l-5.8-7.58-6.63 7.58H.48l8.6-9.83L0 1.5h7.59l5.24 6.93 6.07-6.93Zm-1.29 18.8h2.04L6.48 3.6H4.29l13.32 16.7Z" />
</svg>
);
const PROVIDERS: Provider[] = [
{
id: "google",
label: "Google",
glyph: GoogleGlyph,
solid:
"bg-white text-slate-700 border border-slate-300 hover:bg-slate-50 dark:bg-zinc-900 dark:text-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800",
ring: "focus-visible:ring-sky-500",
},
{
id: "apple",
label: "Apple",
glyph: AppleGlyph,
solid:
"bg-neutral-900 text-white border border-neutral-900 hover:bg-neutral-800 dark:bg-white dark:text-neutral-900 dark:border-white dark:hover:bg-neutral-100",
ring: "focus-visible:ring-neutral-500",
},
{
id: "github",
label: "GitHub",
glyph: GitHubGlyph,
solid:
"bg-[#24292f] text-white border border-[#24292f] hover:bg-[#1c2126] dark:bg-zinc-100 dark:text-zinc-900 dark:border-zinc-100 dark:hover:bg-white",
ring: "focus-visible:ring-violet-500",
},
{
id: "microsoft",
label: "Microsoft",
glyph: MicrosoftGlyph,
solid:
"bg-white text-slate-700 border border-slate-300 hover:bg-slate-50 dark:bg-zinc-900 dark:text-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800",
ring: "focus-visible:ring-sky-500",
},
{
id: "facebook",
label: "Facebook",
glyph: FacebookGlyph,
solid:
"bg-[#1877F2] text-white border border-[#1877F2] hover:bg-[#0f66d8] dark:hover:bg-[#3b8bf5]",
ring: "focus-visible:ring-sky-400",
},
{
id: "x",
label: "X",
glyph: XGlyph,
solid:
"bg-black text-white border border-black hover:bg-neutral-800 dark:bg-white dark:text-black dark:border-white dark:hover:bg-neutral-100",
ring: "focus-visible:ring-neutral-500",
},
];
const Spinner = ({ className = "" }: { className?: string }) => (
<svg
viewBox="0 0 24 24"
width="18"
height="18"
aria-hidden="true"
className={`bss-spin ${className}`}
>
<circle
cx="12"
cy="12"
r="9"
fill="none"
stroke="currentColor"
strokeOpacity="0.25"
strokeWidth="3"
/>
<path
d="M12 3a9 9 0 0 1 9 9"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
/>
</svg>
);
const Check = ({ className = "" }: { className?: string }) => (
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" className={className}>
<path
d="M20 6 9 17l-5-5"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
type Status = "idle" | "loading" | "done";
export default function BtnSocialSet() {
const reduce = useReducedMotion();
const [status, setStatus] = useState<Record<string, Status>>({});
const activate = (key: string) => {
setStatus((prev) => {
if (prev[key] === "loading") return prev;
return { ...prev, [key]: "loading" };
});
window.setTimeout(
() => setStatus((prev) => ({ ...prev, [key]: "done" })),
reduce ? 350 : 1300
);
window.setTimeout(
() => setStatus((prev) => ({ ...prev, [key]: "idle" })),
reduce ? 1600 : 3000
);
};
const stateOf = (key: string): Status => status[key] ?? "idle";
return (
<section className="relative w-full bg-slate-50 px-5 py-20 text-slate-900 dark:bg-zinc-950 dark:text-zinc-50 sm:px-8">
<style>{`
@keyframes bss-spin-kf { to { transform: rotate(360deg); } }
@keyframes bss-pop-kf {
0% { transform: scale(0.4); opacity: 0; }
60% { transform: scale(1.15); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
.bss-spin { animation: bss-spin-kf 0.8s linear infinite; transform-origin: center; }
.bss-pop { animation: bss-pop-kf 0.35s cubic-bezier(0.34,1.56,0.64,1) both; }
.bss-press { transition: transform 0.12s ease, box-shadow 0.2s ease, background-color 0.18s ease, border-color 0.18s ease; }
.bss-press:active:not(:disabled) { transform: translateY(1px) scale(0.99); }
@media (prefers-reduced-motion: reduce) {
.bss-spin { animation-duration: 1.4s; }
.bss-pop { animation: none; }
.bss-press { transition: none; }
.bss-press:active:not(:disabled) { transform: none; }
}
`}</style>
<div className="mx-auto max-w-2xl">
<header className="mb-10 text-center">
<span className="inline-flex items-center gap-1.5 rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs font-medium tracking-wide text-indigo-700 dark:border-indigo-900/60 dark:bg-indigo-950/50 dark:text-indigo-300">
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
Continue with
</span>
<h2 className="mt-4 text-2xl font-semibold tracking-tight sm:text-3xl">
Social sign-in buttons
</h2>
<p className="mx-auto mt-2 max-w-md text-sm text-slate-500 dark:text-zinc-400">
Tactile, accessible OAuth buttons with real loading and success
states. Click one to see it work.
</p>
</header>
{/* Variant 1 — full-width solid stack */}
<div className="mb-14">
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-slate-400 dark:text-zinc-500">
Full width
</p>
<div className="space-y-3">
{PROVIDERS.slice(0, 4).map((p) => {
const key = `full-${p.id}`;
const s = stateOf(key);
return (
<button
key={key}
type="button"
onClick={() => activate(key)}
disabled={s === "loading"}
aria-label={`Continue with ${p.label}`}
className={`bss-press flex w-full items-center justify-center gap-3 rounded-xl px-4 py-3 text-sm font-semibold shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 disabled:cursor-not-allowed disabled:opacity-70 dark:focus-visible:ring-offset-zinc-950 ${p.solid} ${p.ring}`}
>
<span className="flex h-[18px] w-[18px] items-center justify-center">
{s === "loading" ? (
<Spinner />
) : s === "done" ? (
<Check className="bss-pop" />
) : (
p.glyph
)}
</span>
<span>
{s === "loading"
? "Connecting…"
: s === "done"
? "Signed in"
: `Continue with ${p.label}`}
</span>
</button>
);
})}
</div>
</div>
{/* Variant 2 — outline / secondary style */}
<div className="mb-14">
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-slate-400 dark:text-zinc-500">
Outline
</p>
<div className="grid gap-3 sm:grid-cols-2">
{PROVIDERS.map((p) => {
const key = `outline-${p.id}`;
const s = stateOf(key);
return (
<button
key={key}
type="button"
onClick={() => activate(key)}
disabled={s === "loading"}
aria-label={`Sign in with ${p.label}`}
className={`bss-press flex items-center justify-center gap-2.5 rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 shadow-sm outline-none hover:border-slate-400 hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 disabled:cursor-not-allowed disabled:opacity-70 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:hover:border-zinc-600 dark:hover:bg-zinc-800 dark:focus-visible:ring-offset-zinc-950 ${p.ring}`}
>
<span className="flex h-[18px] w-[18px] items-center justify-center">
{s === "loading" ? (
<Spinner />
) : s === "done" ? (
<Check className="bss-pop text-emerald-500" />
) : (
p.glyph
)}
</span>
<span>
{s === "done" ? "Connected" : p.label}
</span>
</button>
);
})}
</div>
</div>
{/* Variant 3 — icon-only round */}
<div className="mb-14">
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-slate-400 dark:text-zinc-500">
Icon only
</p>
<div className="flex flex-wrap items-center justify-center gap-3">
{PROVIDERS.map((p) => {
const key = `icon-${p.id}`;
const s = stateOf(key);
return (
<button
key={key}
type="button"
onClick={() => activate(key)}
disabled={s === "loading"}
aria-label={`Sign in with ${p.label}`}
className={`bss-press flex h-12 w-12 items-center justify-center rounded-full border border-slate-300 bg-white text-slate-700 shadow-sm outline-none hover:border-slate-400 hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 disabled:cursor-not-allowed disabled:opacity-70 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:hover:border-zinc-600 dark:hover:bg-zinc-800 dark:focus-visible:ring-offset-zinc-950 ${p.ring}`}
>
{s === "loading" ? (
<Spinner />
) : s === "done" ? (
<Check className="bss-pop text-emerald-500" />
) : (
p.glyph
)}
</button>
);
})}
</div>
</div>
{/* Variant 4 — compact pill row with divider */}
<div>
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-slate-400 dark:text-zinc-500">
Compact
</p>
<div className="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<div className="flex flex-wrap justify-center gap-2.5">
{PROVIDERS.slice(0, 3).map((p) => {
const key = `pill-${p.id}`;
const s = stateOf(key);
return (
<button
key={key}
type="button"
onClick={() => activate(key)}
disabled={s === "loading"}
aria-label={`Continue with ${p.label}`}
className={`bss-press inline-flex items-center gap-2 rounded-full px-4 py-2 text-sm font-semibold shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-70 dark:focus-visible:ring-offset-zinc-900 ${p.solid} ${p.ring}`}
>
<span className="flex h-[18px] w-[18px] items-center justify-center">
{s === "loading" ? (
<Spinner />
) : s === "done" ? (
<Check className="bss-pop" />
) : (
p.glyph
)}
</span>
{p.label}
</button>
);
})}
</div>
<div className="my-5 flex items-center gap-3">
<span className="h-px flex-1 bg-slate-200 dark:bg-zinc-800" />
<span className="text-xs font-medium uppercase tracking-wider text-slate-400 dark:text-zinc-500">
or
</span>
<span className="h-px flex-1 bg-slate-200 dark:bg-zinc-800" />
</div>
<button
type="button"
onClick={() => activate("email")}
disabled={stateOf("email") === "loading"}
className="bss-press flex w-full items-center justify-center gap-2 rounded-xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm outline-none hover:bg-indigo-500 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-70 dark:focus-visible:ring-offset-zinc-900"
>
{stateOf("email") === "loading" ? (
<Spinner />
) : stateOf("email") === "done" ? (
<Check className="bss-pop" />
) : (
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="5" width="18" height="14" rx="2" />
<path d="m3 7 9 6 9-6" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)}
{stateOf("email") === "loading"
? "Sending link…"
: stateOf("email") === "done"
? "Check your inbox"
: "Continue with email"}
</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.

