Skip to content

Compound components

Epic React


set of components that share state create a simple, powerful declarative API

Why Compound Components

  1. Breaks things down 2. Makes things easier to change 3. need to make a change? it's clear which sub-element you should change
  2. Flexible! 5. re-order the inside components


  • like Table and Column
  • where Column expects a bunch of props from Table - but we don't set it Explicitly


    {/* `Table` gives `Column` some props behind the scenes */}
    <Column />
    <Column />



Epic React Toggle Example

Super flexible!

  • You can change the order of the children!
  • or straight up just remove any part
    <ToggleOn>The button is on</ToggleOn>
    <ToggleButton />
    <ToggleOff>The button is off</ToggleOff>



React.cloneElement() API

React.cloneElement(element, [config], [...children]);

Why do we need cloneElement()?

  • like creating a new copy of array
  • parent (which has the state) can't modify props directly
    • React doesn't let you modify the child.props

Flexible Compound Components uses React Context

  • passes props to any descendant
  • so we don't need cloneElement

Passing in unexpected children


Errors out

Invalid value for prop `toggle` on <span> tag.

Allowed List of components

const ALLOWED_TYPES = [ToggleOn, ToggleOff, ToggleButton];

if (ALLOWED_TYPES.includes(child.type)) {
    return React.cloneElement(child, { on, toggle });
return child;

Blacklist/denylist: ignore HTML elements

more flexible

typeof child.type === "string"; // HTML element


return, (child, index) => {
    return React.cloneElement(child, {
        id: `i-am-child-${index}`,

const ToggleOn = ({ on, children }) => on && children;

// Accepts `on` and `children` props and returns `children` if `on` is false
const ToggleOff = ({ on, children }) => !on && children;

// Accepts `on` and `toggle` props and returns the <Switch /> with those props.
const ToggleButton = ({ on, toggle }) => <Switch on={on} onClick={toggle} />;

Passing in a React fragment

If children is a Fragment it will be treated as a single child and not traversed.

const childrenArr = React.Children.toArray(children);

const extraElements = childrenArr.filter(
    (el) => el.props.displayName === "extraElement"
const sectionChildren = childrenArr.filter(
    (el) => el.type.displayName === "Section"

Flexible Compound Components

  • instead of passing props via cloneElement
  • we pass down data via Context and useContext

See Compound Components

Last update: 2023-04-24