The Compound Components Pattern lets related components share internal state while exposing a flexible, declarative API.
A traditional component requires consumers to manage and pass state explicitly.
<Toggleon={on}onToggle={toggle}/>
As the component grows, its API can become harder to manage. Consumers may need to coordinate multiple props to control related UI elements.
Compound components let consumers compose related components under a common parent.
<Toggle><Toggle.On>ON</Toggle.On><Toggle.Off>OFF</Toggle.Off><Toggle.Button /></Toggle>
The parent manages the shared state, while its children access that state implicitly through React Context.
The parent provides the state through Context. Child components consume it using useContext.
import {createContext,useContext,useState,type ReactNode,} from "react";type ToggleContextValue = {on: boolean;toggle: () => void;};const ToggleContext = createContext<ToggleContextValue | undefined>(undefined);function useToggle() {const context = useContext(ToggleContext);if (!context) {throw new Error("Toggle components must be used within <Toggle>");}return context;}function Toggle({ children }: { children: ReactNode }) {const [on, setOn] = useState(false);const toggle = () => setOn(value => !value);return (<ToggleContext.Provider value={{ on, toggle }}>{children}</ToggleContext.Provider>);}function On({ children }: { children: ReactNode }) {const { on } = useToggle();return on ? children : null;}function Off({ children }: { children: ReactNode }) {const { on } = useToggle();return on ? null : children;}function Button() {const { on, toggle } = useToggle();return (<button type="button" onClick={toggle} aria-pressed={on}>{on ? "Turn off" : "Turn on"}</button>);}Toggle.On = On;Toggle.Off = Off;Toggle.Button = Button;export { Toggle };
Usage:
<Toggle><Toggle.On>The toggle is ON</Toggle.On><Toggle.Off>The toggle is OFF</Toggle.Off><Toggle.Button /></Toggle>