Buttons carry more state than they look like they do — variants, sizes, disabled, and (the one part that needs JS) a loading state for actions that take time.
Pseudocode
Structure
A single <button> (or <a class="btn"> when it navigates instead of acting) — classes control everything else, the element stays the same.
Style
A base .btn class for the shared shape and spacing, then modifier classes (.btn-primary, .btn-outline, .btn-sm/.btn-lg) that only touch color or size — the classic “base + modifier” pattern.
:disabled needs its own dimmed, no-cursor styling; the browser’s default disabled look is barely noticeable.
Features
Variants and sizes are pure CSS.
A “loading” state (spinner + disabled, so a slow action can’t be double-submitted) needs a little JS to toggle it on click and back off once the action resolves.
Clicking disables the button and swaps its label for a spinner + “Saving…”, so a slow action can’t fire twice. The setTimeout here stands in for a real request — swap it for whatever await fetch(...) resolves.