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?
Top comments (0)