Why Your Reusable Components Keep Breaking (And How to Fix Your API Design)
Ever stared at a component library you built just three weeks ago, only to realize it's already suffocating under a mountain of boolean props like hasBadge, isCompact, and withIcon? I ran into this exact wall recently w
Ever stared at a component library you built just three weeks ago, only to realize it's already suffocating under a mountain of boolean props like hasBadge, isCompact, and withIcon?
I ran into this exact wall recently while refactoring a set of modular landing page cards for a mixed-media client project. What started as a clean, reusable UI module quickly devolved into a brittle spaghetti monster the moment a new layout requirement dropped. Every time a client needed a tiny structural tweakβlike shifting an image from top to side, or adding a secondary action tagβI found myself cracking open the core component file and risking regressions across the entire layout.
The underlying problem isn't just poor planning; it's treating components like rigid black boxes instead of flexible composition primitives.
Here is what that trap looks like in code:
// The Trap: A monolithic component buckling under conditional props
function ProductCard({ title, price, badgeText, isLarge, hasImage, imageSrc, variant }) {
return (
<div className={`card ${variant} ${isLarge ? 'large' : ''}`}>
{hasImage && <img src={imageSrc} alt={title} />}
{badgeText && <span className="badge">{badgeText}</span>}
<h3>{title}</h3>
<p>{price}</p>
</div>
);
}
To break out of this cycle, I had to shift away from monolithic prop drilling and lean into compound component patternsβhanding structural control back to the consumer while keeping styles neatly encapsulated:
// The Fix: Composable layout primitives
function Card({ children, className }) {
return <div className={`card-base ${className || ''}`}>{children}</div>;
}
Card.Header = function CardHeader({ children }) {
return <div className="card-header">{children}</div>;
};
Card.Body = function CardBody({ children }) {
return <div className="card-body">{children}</div>;
};
// Usage: Clean, extensible, and untouched core logic
export default function App() {
return (
<Card>
<Card.Header>
<span className="badge">Featured</span>
<img src="/assets/preview.svg" alt="Preview" />
</Card.Header>
<Card.Body>
<h3>Dynamic System Spec</h3>
<p>Structured layout tokens in motion.</p>
</Card.Body>
</Card>
);
}
My question:
How do you usually handle this in your own codebases? Do you enforce strict, heavily-propped components to keep teams locked into a rigid design system, or have you shifted toward compound composition patterns to handle custom layout variations? How do you keep things maintainable?
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.