Here's a failure mode that shows up in almost every growing component library: a team adopts composition patterns to escape prop explosion, and six months later they've traded one kind of complexity for another. The components are more flexible, technically. They're also harder to use correctly, easier to misuse, and require three files to do what one prop used to do.
Composition patterns are genuinely good architecture. The problem is applying them at the wrong layer.
Prop Explosion Is Real, But It's a Symptom
The case for composition usually starts with a familiar scene. Maksym Kuzmitskyi's writeup on compound components describes it precisely: a <Select> starts clean, then someone needs a divider between groups, someone else needs an icon, then a sticky footer row, then a disabled option with a tooltip. Six months later the component takes twenty-three props, half undocumented, and every new requirement means editing the component and hoping you didn't break the other twenty-two use cases.
The diagnosis is correct. Props are the wrong tool the moment a caller needs control over structure — what goes where, what sits between things, what wraps what. Passing dividerAfter={[2, 5]} to describe where a line should appear is a smell: you're negotiating layout through a configuration language you invented and now have to maintain forever.
Compound components solve this by inverting control. Instead of one component that owns structure, you expose a set of components the caller arranges themselves. The caller places <Select.Divider> wherever they want it. The component stops knowing about every caller's layout wishes.
That's the right move — for that component, at that layer.
The Ladder You Don't Have to Climb All the Way
Where teams go wrong is treating composition as a universal upgrade. The engineers at Orus documented this directly in their writeup on how their frontend converged on composition patterns: they describe it as a ladder with four rungs, where the first rung is plain props, and the explicit rule is to start at the bottom and only climb when a concrete pain pushes you up. Most of their components, they note, never need to climb past rung two.
That framing matters because the cost of each rung is real. Compound components introduce indirection. Headless components — which Kuzmitskyi covers in a companion post — go further, rendering nothing at all and handing all markup decisions to the caller. That's genuinely powerful when you need the same behavior across two products with different designs. It's pure overhead when you have one product and a deadline.
The Honcho glossary entry on compound vs. composite patterns puts a useful rule of thumb on this: if subcomponents need shared state to function correctly, use compound. If they're just grouping, use composite — or just props. Most modern design systems use a mix: low-level components like Button and Input stay composite (standalone, no shared state required), while high-level components like Dialog and Tabs go compound because the subcomponents genuinely depend on coordinated state.
Wrapping a <Button> in a compound component structure because you might someday need it to coordinate with siblings is the kind of bet that creates busywork. The abstraction isn't earning its place.
The Specific Friction Wrapper Components Introduce
When composition patterns get applied too eagerly, the friction shows up in predictable places.
The first is discoverability. A <DashboardCard> with a headerAction slot — as illustrated in the DEV Community post on god components — is more flexible than one with a showButton boolean. It's also less obvious to a developer who just needs to put a button in the header. Props are self-documenting in a way that slots aren't. You can see all the props in autocomplete. You can't see all the valid things to put in a slot.
The second is over-composition at the wrong layer. The Orus team's Item row component is a good example of getting this right: collapsed stays a plain prop on Item.Root because it's genuine row state — the root's own responsibility. Not everything becomes a slot. The discipline is knowing which decisions belong to the component and which belong to the caller. Turning everything into a slot doesn't reduce coupling; it just moves the coupling into the caller's assembly code.
The third is the wrapper proliferation that follows. Once composition is the team's default pattern, every new use case gets a new wrapper. You end up with <AdminDashboardCard>, <MetricsDashboardCard>, and <ReportDashboardCard> — each one a thin wrapper around the same composed structure, each one slightly different, none of them in the design system. This is the fourteen-button problem in a different costume.
The Signal Worth Watching
The question to ask before reaching for composition isn't "would this be more flexible as slots?" It's "is the caller currently asking for control over structure, or just configuration?" A boolean prop that toggles a loading state is configuration. An array of indices describing where dividers should appear is a caller asking for structural control in the only language you've given them.
When you see that second pattern — callers negotiating layout through increasingly baroque prop combinations — that's the signal to climb the ladder. Until then, stay on rung one. The wrapper you don't write is the wrapper that can't create friction.
