Component patterns (1): Composition

When designing component APIs, it’s tempting to add more props to cover every possible variation: icon, iconPosition, label, and so on. At first, this feels convenient. But over time, these props pile up, the logic inside the component becomes harder to follow, and developers lose flexibility.

A simpler and more scalable way is to lean on composition — letting developers describe the content and layout directly with children instead of juggling props.

Prop-driven content

In many component libraries, content and layout are controlled with props like icon, iconPosition, and label.

<Button icon="Play" iconPosition="leading" label="Play" />

This works for simple cases, but as soon as you add variations, the number of props grows. The API becomes harder to maintain, and layout logic gets buried inside the component.

A better approach: Composition

Instead of passing everything as props, use composition and children.

<Button>
  <Icon name="Play" />
  Play
</Button>

Why this works better

Developers have full control over content and order.

The number of props is reduced, keeping the API clean.

It matches React’s composition model.

Layout is handled by children and CSS, not internal logic.

It is easier to extend components without changing the API.

But is this too much freedom?

Some worry that composition allows misuse, like adding two icons to a button. But I believe that :

const AddToCartButton = () => (
  <Button>
    {state === "added" ? <Icon name="Check" /> : <Icon name="Plus" />}
    Add to cart
  </Button>
);

With the props-driven approach, this would require a long list like icon, iconWhenAdded, label, labelWhenAdded. With composition, the code stays natural and readable.

So, yeah, looks like we want composition. But how are we going to implement it? More on that on Component patterns: Polymorphism