Solid Set Button
Original · freeA set of solid buttons: primary, secondary, success and danger, in three sizes.
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-solid-set.json"use client";
import { useState, type ReactNode } from "react";
type Variant = "primary" | "secondary" | "success" | "danger";
type Size = "sm" | "md" | "lg";
const VARIANT_CLASSES: Record<Variant, string> = {
primary:
"bg-indigo-600 text-white shadow-sm shadow-indigo-600/25 hover:bg-indigo-500 active:bg-indigo-700 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:active:bg-indigo-600 dark:shadow-indigo-500/20 dark:focus-visible:outline-indigo-400",
secondary:
"bg-slate-800 text-white shadow-sm shadow-slate-900/25 hover:bg-slate-700 active:bg-slate-900 focus-visible:outline-slate-700 dark:bg-slate-200 dark:text-slate-900 dark:hover:bg-white dark:active:bg-slate-300 dark:shadow-black/20 dark:focus-visible:outline-slate-300",
success:
"bg-emerald-600 text-white shadow-sm shadow-emerald-600/25 hover:bg-emerald-500 active:bg-emerald-700 focus-visible:outline-emerald-600 dark:bg-emerald-500 dark:hover:bg-emerald-400 dark:active:bg-emerald-600 dark:shadow-emerald-500/20 dark:focus-visible:outline-emerald-400",
danger:
"bg-rose-600 text-white shadow-sm shadow-rose-600/25 hover:bg-rose-500 active:bg-rose-700 focus-visible:outline-rose-600 dark:bg-rose-500 dark:hover:bg-rose-400 dark:active:bg-rose-600 dark:shadow-rose-500/20 dark:focus-visible:outline-rose-400",
};
const SIZE_CLASSES: Record<Size, string> = {
sm: "h-8 gap-1.5 rounded-lg px-3 text-xs",
md: "h-10 gap-2 rounded-lg px-4 text-sm",
lg: "h-12 gap-2.5 rounded-xl px-6 text-base",
};
const SPINNER_SIZE: Record<Size, string> = {
sm: "h-3.5 w-3.5",
md: "h-4 w-4",
lg: "h-5 w-5",
};
const ICON_SIZE: Record<Size, string> = {
sm: "h-3.5 w-3.5",
md: "h-4 w-4",
lg: "h-5 w-5",
};
interface SolidButtonProps {
variant: Variant;
size: Size;
children: ReactNode;
loading?: boolean;
disabled?: boolean;
icon?: ReactNode;
onClick?: () => void;
ariaLabel?: string;
}
function SolidButton({
variant,
size,
children,
loading = false,
disabled = false,
icon,
onClick,
ariaLabel,
}: SolidButtonProps) {
return (
<button
type="button"
onClick={onClick}
disabled={disabled || loading}
aria-label={ariaLabel}
aria-busy={loading}
className={[
"relative inline-flex select-none items-center justify-center font-semibold tracking-tight",
"transition-[background-color,transform,box-shadow] duration-150 ease-out",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2",
"active:translate-y-px",
"disabled:pointer-events-none disabled:opacity-50",
SIZE_CLASSES[size],
VARIANT_CLASSES[variant],
].join(" ")}
>
{loading && (
<span
className={[
"bsbs-spin shrink-0 rounded-full border-2 border-current border-r-transparent",
SPINNER_SIZE[size],
].join(" ")}
aria-hidden="true"
/>
)}
{!loading && icon && (
<span className={["shrink-0", ICON_SIZE[size]].join(" ")} aria-hidden="true">
{icon}
</span>
)}
<span className={loading ? "opacity-90" : ""}>{children}</span>
</button>
);
}
const BoltIcon = (
<svg viewBox="0 0 24 24" fill="none" className="h-full w-full" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round">
<path d="M13 2 4 14h6l-1 8 9-12h-6l1-8Z" />
</svg>
);
const CartIcon = (
<svg viewBox="0 0 24 24" fill="none" className="h-full w-full" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round">
<circle cx="9" cy="20" r="1.4" />
<circle cx="18" cy="20" r="1.4" />
<path d="M2 3h2.2l2.1 12.2a1.5 1.5 0 0 0 1.5 1.3h9.1a1.5 1.5 0 0 0 1.5-1.2L20.5 7H5.3" />
</svg>
);
const CheckIcon = (
<svg viewBox="0 0 24 24" fill="none" className="h-full w-full" stroke="currentColor" strokeWidth={2.6} strokeLinecap="round" strokeLinejoin="round">
<path d="m5 12.5 4.5 4.5L19 6.5" />
</svg>
);
const TrashIcon = (
<svg viewBox="0 0 24 24" fill="none" className="h-full w-full" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round">
<path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" />
</svg>
);
const sizes: { key: Size; label: string }[] = [
{ key: "sm", label: "Small" },
{ key: "md", label: "Medium" },
{ key: "lg", label: "Large" },
];
const variants: {
key: Variant;
label: string;
icon: ReactNode;
cta: string;
}[] = [
{ key: "primary", label: "Primary", icon: BoltIcon, cta: "Deploy to production" },
{ key: "secondary", label: "Secondary", icon: CartIcon, cta: "Add to cart" },
{ key: "success", label: "Success", icon: CheckIcon, cta: "Confirm order" },
{ key: "danger", label: "Danger", icon: TrashIcon, cta: "Delete workspace" },
];
export default function BtnSolidSet() {
const [savingKey, setSavingKey] = useState<Variant | null>(null);
const [disabledMode, setDisabledMode] = useState(false);
const handleClick = (key: Variant) => {
setSavingKey(key);
window.setTimeout(() => setSavingKey(null), 1600);
};
return (
<section className="relative w-full bg-slate-50 px-6 py-20 dark:bg-slate-950 sm:px-8">
<style>{`
@keyframes bsbs-spin {
to { transform: rotate(360deg); }
}
.bsbs-spin { animation: bsbs-spin 0.6s linear infinite; }
@media (prefers-reduced-motion: reduce) {
.bsbs-spin { animation-duration: 1.4s; }
}
`}</style>
<div className="mx-auto max-w-4xl">
<header className="mb-12">
<span className="inline-flex items-center rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300">
Buttons
</span>
<h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-4xl">
Solid button set
</h2>
<p className="mt-3 max-w-2xl text-base leading-relaxed text-slate-600 dark:text-slate-400">
Four intents — primary, secondary, success and danger — each in three
sizes. Tactile press, focus-visible rings, and real loading and
disabled states.
</p>
</header>
<div className="mb-10 flex flex-wrap items-center gap-3">
<button
type="button"
onClick={() => setDisabledMode((v) => !v)}
aria-pressed={disabledMode}
className="inline-flex h-9 items-center gap-2 rounded-lg border border-slate-300 bg-white px-3 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800"
>
<span
className={[
"flex h-4 w-8 items-center rounded-full p-0.5 transition-colors",
disabledMode ? "bg-indigo-500" : "bg-slate-300 dark:bg-slate-600",
].join(" ")}
aria-hidden="true"
>
<span
className={[
"h-3 w-3 rounded-full bg-white transition-transform",
disabledMode ? "translate-x-4" : "translate-x-0",
].join(" ")}
/>
</span>
Disabled preview
</button>
<p className="text-sm text-slate-500 dark:text-slate-500">
Tap any button to watch its loading state.
</p>
</div>
<div className="space-y-4">
{variants.map((v) => (
<div
key={v.key}
className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900"
>
<div className="mb-5 flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">
{v.label}
</h3>
<code className="rounded bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-500 dark:bg-slate-800 dark:text-slate-400">
{v.key}
</code>
</div>
<div className="flex flex-wrap items-center gap-4">
{sizes.map((s) => (
<SolidButton
key={s.key}
variant={v.key}
size={s.key}
icon={v.icon}
disabled={disabledMode}
loading={savingKey === v.key}
onClick={() => handleClick(v.key)}
>
{savingKey === v.key ? "Working…" : v.cta}
</SolidButton>
))}
</div>
</div>
))}
</div>
<div className="mt-8 rounded-2xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<h3 className="mb-5 text-sm font-semibold text-slate-900 dark:text-white">
Icon-only
</h3>
<div className="flex flex-wrap items-center gap-4">
<button
type="button"
aria-label="Confirm and save"
className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-emerald-600 text-white shadow-sm shadow-emerald-600/25 transition-[background-color,transform] duration-150 hover:bg-emerald-500 active:translate-y-px active:bg-emerald-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-600 dark:bg-emerald-500 dark:hover:bg-emerald-400 dark:focus-visible:outline-emerald-400"
>
<span className="h-5 w-5" aria-hidden="true">
{CheckIcon}
</span>
</button>
<button
type="button"
aria-label="Delete item"
className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-rose-600 text-white shadow-sm shadow-rose-600/25 transition-[background-color,transform] duration-150 hover:bg-rose-500 active:translate-y-px active:bg-rose-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-600 dark:bg-rose-500 dark:hover:bg-rose-400 dark:focus-visible:outline-rose-400"
>
<span className="h-5 w-5" aria-hidden="true">
{TrashIcon}
</span>
</button>
<button
type="button"
aria-label="Quick deploy"
className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-indigo-600 text-white shadow-sm shadow-indigo-600/25 transition-[background-color,transform] duration-150 hover:bg-indigo-500 active:translate-y-px active:bg-indigo-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus-visible:outline-indigo-400"
>
<span className="h-5 w-5" aria-hidden="true">
{BoltIcon}
</span>
</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.

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.

Pill Group Button
OriginalA segmented pill button group with a sliding active indicator.

