Component variants and a small design system
A design system codifies recurring decisions: button sizes, semantic colors, radii, typography, spacing, and interaction states. Variants should be explicit and finite—for example primary, secondary, and danger—so components remain predictable and source detection remains reliable.
Do not create an abstraction for every element. Start with repeated patterns that need consistent behavior or that will be changed centrally. Document variant intent and include disabled, focus, loading, and error states where relevant.
Design for states, not just screenshots
For every reusable control, consider default, hover, focus, active, disabled, loading, success, and error states where those states apply.
Explicit button variants
const variants = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "border border-slate-300 bg-white text-slate-900 hover:bg-slate-50",
danger: "bg-red-600 text-white hover:bg-red-700",
};
function ActionButton({ variant = "primary", children, ...props }) {
const variantClass = variants[variant] ?? variants.primary;
return (
<button
{...props}
className={`rounded-lg px-4 py-2.5 text-sm font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 ${variantClass}`}
>
{children}
</button>
);
}💡 The finite map centralizes visual choices and ensures all supported classes appear in source. In a TypeScript app, type
variant from keyof typeof variants; consider whether callers should be allowed to override classes at all.