The compound component pattern that makes React APIs feel like magic
← Back
April 4, 2026React7 min read

The compound component pattern that makes React APIs feel like magic

Published April 4, 20267 min read

I was building a Select component for our design system and ended up with a 30-prop API to handle all the variations: custom option rendering, grouped options, search, clearable, multi-select. Then I found the compound component pattern and realized I had been thinking about component APIs wrong. Here is the pattern that changed how I design components.

The problem: monolithic components

The naive approach gives every feature its own prop:

typescript
// Nobody wants to use a component with this API

  
  
    
    
      Apple
      Banana
    
    
      Carrot
    
  

Building the compound component

typescript
import { createContext, useContext, useState, useId } from 'react';

// Shared context — the glue between subcomponents
interface SelectContextValue {
  value: string;
  onChange: (value: string) => void;
  isOpen: boolean;
  setIsOpen: (open: boolean) => void;
  search: string;
  setSearch: (s: string) => void;
  labelId: string;
  menuId: string;
}

const SelectContext = createContext(null);

function useSelectContext() {
  const ctx = useContext(SelectContext);
  if (!ctx) throw new Error('Select subcomponents must be used inside  setSearch(e.target.value)}
        placeholder="Search..."
        className="select-search"
        onClick={(e) => e.stopPropagation()}
      />
    
  );
}

function Group({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    
  • {label}
      {children}
  • ); } function Option({ value, children }: { value: string; children: React.ReactNode }) { const { value: selected, onChange, setIsOpen, search } = useSelectContext(); // Filter by search if (search && !String(children).toLowerCase().includes(search.toLowerCase())) { return null; } return (
  • { onChange(value); setIsOpen(false); }} className={`select-option ${selected === value ? 'selected' : ''}`} > {children}
  • ); } // Assemble the compound component export const Select = Object.assign(SelectRoot, { Trigger, Menu, Search, Group, Option, });

    Why the pattern is powerful

    Consumers can compose exactly the UI they need without waiting for a new prop to be added to the parent:

    typescript
    // Simple use case — no groups, no search
    
    
    // Complex use case — groups + search, custom trigger
    

    The compound component pattern is used by Radix UI, Headless UI, and most modern design systems. Once you understand it, you will never go back to monolithic component APIs.

    Share this
    ← All Posts7 min read