01The Idea That Predates the Hype

Before React popularised the component model, before Vue made it approachable, and well before Svelte compiled it away entirely, MontageJS was quietly exploring the same core insight: user interfaces should be built from discrete, self-contained pieces that fit together cleanly. The framing was different — MontageJS talked about objects and bindings rather than props and state — but the underlying conviction was identical. A complex UI is a composition problem. Solve composition and you solve most of the rest.

That conviction has aged better than almost anything else in front-end history. Every framework that matters today organises UI as a tree of components. The debates are about syntax, performance, and rendering strategy — not about whether components are the right unit of abstraction. That argument is over.

What isn't settled is how to compose them well.

Components take what they need through props, not from the world.

02What Good Composition Actually Looks Like

The mistake most developers make early — and many make for years — is conflating having components with composing them well. You can slice a UI into dozens of components and still end up with something rigid and hard to change, because the components are coupled in the wrong ways.

Good composition has a few recognisable properties.

Components take what they need through props, not from the world. A Button that reaches into a global store to decide its own label is not a composable component — it's a script with a JSX face. Composable components are honest about their inputs. Everything they need arrives from outside; everything they produce leaves through callbacks or events. This makes them testable in isolation, reusable in contexts their original author didn't anticipate, and predictable when something breaks.

Composition favours containment over inheritance. Rather than extending a BaseCard into a ProductCard into a FeaturedProductCard, you pass children. React's children prop, Vue's named slots, Svelte's <slot> — these all express the same idea. An outer component defines structure and behaviour; an inner component provides content. The seam between them is explicit and narrow.

jsx
// Containment: the shell doesn't care what's inside
function Card({ children, className }) {
  return <div className={`card ${className ?? ''}`}>{children}</div>;
}

// At the call site, the content is the caller's responsibility
<Card className="featured">
  <ProductImage src={product.image} />
  <ProductTitle>{product.name}</ProductTitle>
</Card>

A Card that simply renders whatever children it is given works anywhere. It has no opinion about products. Inheritance would have locked it in.

Small components, composed late. The instinct when building a form is to reach for a <UserProfileForm> that handles everything. Resist it. A <FieldGroup>, a <ValidatedInput>, a <SubmitButton> — each handling its one concern — compose into a form and into a dozen other forms you haven't designed yet. Specificity should live at the call site, not inside the component.

This is the genuine insight from decades of component-based thinking: the value of a component is proportional to how many places it fits, not how much it does.

03The Composition Patterns Worth Knowing

Three patterns solve most real composition problems.

Render props and slots pass display responsibility back up the tree. When a Dropdown needs to render arbitrary item content — a product card, a user avatar, a flag emoji plus country name — you pass a render function or slot instead of trying to enumerate every variant. The Dropdown handles open/close, keyboard navigation, and positioning; the caller handles pixels.

Compound components let a group of related components share state implicitly, keeping the public API clean. HTML's select and option elements are the canonical browser example. In userland: a <Tabs> parent that coordinates <TabList>, <Tab>, and <TabPanel> children via context, so the caller never manages the active index directly.

Composition over configuration applies when a component starts accumulating boolean flags — showHeader, collapsible, withFooter, compact. Each flag is a sign that what you really have is two or three components that want to be composed, not configured. Configuration props are fine for styling; behaviour variants usually want genuine separation.

04Where Composition Breaks Down

Composition is not a universal solvent. Components that share a lot of state — a drag-and-drop list, a rich text editor, a canvas-based drawing tool — often have legitimate reasons to be larger and more tightly coupled. Forcing fine-grained composition onto genuinely stateful interactions produces prop-drilling nightmares or context soup. The discipline is knowing which thing you're building.

And state, always, is the hard part. Composable components work beautifully when data flows in one direction: down as props, up as events. The moment two distant components need to agree on shared state, you're outside composition's comfort zone and into state management territory. The two concerns intersect but are not the same.