Variant Pattern: Simple, Predictable Styling APIs
Map variant names to style objects to keep component APIs clean and consistent.
The variant pattern centralizes styling options behind a variant prop. Instead of passing ad-hoc style objects at every call site, you pick a named variant and the component looks up the right styles.
Inline style objects and long conditional classnames scattered across a codebase make components noisy and inconsistent. Two “primary” buttons styled by hand in two places will drift apart, and nobody notices until they end up on the same page.
Styled at the Call Site
Every usage reinventing the button:
function App() {
return (
<div>
<h1>Variant Pattern</h1>
<button style={{ backgroundColor: "blue", color: "white", padding: "8px 16px", margin: "4px" }}>Primary</button>
<button style={{ backgroundColor: "red", color: "white", padding: "8px 16px", margin: "4px" }}>Secondary</button>
</div>
);
}
export default App;
One Map, Named Styles
A variants object and a component that selects from it:
const variants = {
primary: { backgroundColor: "blue", color: "white", padding: "8px 16px", margin: "4px" },
secondary: { backgroundColor: "red", color: "white", padding: "8px 16px", margin: "4px" },
};
function Button({ children, variant = "primary" }) {
const style = variants[variant] || variants.primary;
return <button style={style}>{children}</button>;
}
function App() {
return (
<div>
<h1>Variant Pattern</h1>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
</div>
);
}
export default App;
The fallback to variants.primary means an invalid variant degrades to something sane instead of unstyled. Keep the map next to the component, or in one central theme if the project already has one, and be consistent about which. And variants don’t have to mean colors: size, density, and tone all work as named variants when the component grows.