Eight buttons that cover what most sites and apps need: a primary action, a dark alternative, an outlined secondary, a quiet ghost button, a destructive button, one with a trailing icon, a loading state with a spinner, and a round icon-only button. They share one base class, so sizes and focus rings stay consistent.
export default function ButtonSet() {
const base = "inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-semibold transition focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50";
return (
<div className="flex flex-wrap items-center gap-4 bg-white p-10">
<button type="button" className={`${base} bg-indigo-600 text-white shadow-sm hover:bg-indigo-500 active:bg-indigo-700`}>Primary</button>
<button type="button" className={`${base} bg-slate-900 text-white hover:bg-slate-700`}>Dark</button>
<button type="button" className={`${base} border border-slate-300 bg-white text-slate-900 hover:bg-slate-50`}>Secondary</button>
<button type="button" className={`${base} text-indigo-700 hover:bg-indigo-50`}>Ghost</button>
<button type="button" className={`${base} bg-rose-600 text-white hover:bg-rose-500`}>Delete</button>
<button type="button" className={`${base} bg-indigo-600 text-white hover:bg-indigo-500`}>
Continue
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" className="h-4 w-4"><path fillRule="evenodd" d="M3 10a.75.75 0 0 1 .75-.75h10.64l-3.2-3.2a.75.75 0 1 1 1.06-1.06l4.5 4.5a.75.75 0 0 1 0 1.06l-4.5 4.5a.75.75 0 1 1-1.06-1.06l3.2-3.2H3.75A.75.75 0 0 1 3 10Z" clipRule="evenodd" /></svg>
</button>
<button type="button" disabled className={`${base} bg-indigo-600 text-white`}>
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-4 w-4 animate-spin motion-reduce:animate-none"><circle cx="12" cy="12" r="9" stroke="currentColor" strokeOpacity=".3" strokeWidth="3" /><path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="3" strokeLinecap="round" /></svg>
Saving
</button>
<button type="button" aria-label="Add item" className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-indigo-600 text-white transition hover:bg-indigo-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2">
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" className="h-5 w-5"><path d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z" /></svg>
</button>
</div>
);
}
Install with one command: npx shadcn@latest add https://we.inc/r/button-set.json
The primary button is the one action you most want taken on a screen, so it gets the strongest colour. Secondary and ghost buttons are for other actions such as Cancel or Learn more, and are styled more quietly so they do not compete.
Put a small SVG with the `animate-spin` class before the label and set the `disabled` attribute while the request is running. The `disabled:` classes in the base string fade the button and change the cursor.
Use a button for actions that change something on the page, such as submit, open or delete. Use a link for navigation to another page or section. Both can share the same visual style.
Aim for a touch target of about 44 by 44 pixels. The default here is 40 pixels tall; use the larger padding from the customise notes for primary actions on phones.
Related: Pricing Table with Monthly and Yearly Toggle, Split Hero with Email Capture.
More buttons or all free UI components. Start for free to have a full site built around it.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.