Modern React Composition Patterns
Table of Contents
Composition patterns for building flexible, maintainable React components that scale. These patterns replace boolean-prop proliferation, rigid component APIs, and tangled state with composable, explicit designs.
When to Use
Reference these patterns when:
- A component has more than 3-4 boolean props controlling its behavior
- Building reusable UI components or a shared component library
- Refactoring components that are difficult to extend
- Designing component APIs that other teams will consume
- Reviewing component architecture for flexibility and maintainability
Instructions
- Apply these patterns during component design, code generation, and review. When you see boolean prop accumulation or rigid component APIs, suggest the appropriate composition pattern.
Details
Overview
The core principle: composition over configuration. Instead of adding boolean props and conditional branches to handle every variant, compose smaller, focused components together. This makes components easier to understand, test, and extend — for both humans and AI agents.
1. Replace Boolean Props with Composition
Impact: HIGH — Prevents combinatorial explosion and makes intent explicit.
Boolean props multiply complexity: 4 booleans = 16 possible states, most of which are untested. Replace them with composable children.
Avoid — boolean prop accumulation:
Prefer — explicit composition:
Each piece is explicit, testable, and independently optional.
2. Build Compound Components with Context
Impact: HIGH — Shared implicit state without prop drilling.
Compound components are a group of components that work together, sharing state through context rather than props. The parent owns the state; children consume it.
Avoid — parent manages everything through props:
Prefer — compound components:
Usage:
3. Create Explicit Variant Components
Impact: MEDIUM — Makes each mode a clear, focused component.
When a component has distinct "modes" (dialog vs drawer, inline vs modal, card vs list-item), create explicit variant components instead of toggling with props.
Avoid — one component with mode props:
Prefer — explicit variants:
Each variant has exactly the props it needs — no impossible states, no unused props.
4. Use Children Over Render Props for Composition
Impact: MEDIUM — Simpler API, better readability.
Render props (renderHeader, renderItem) were essential before hooks, but today children provides cleaner composition for most cases.
Avoid — render prop proliferation:
Prefer — children composition:
Reserve render props for cases where the parent needs to provide data to the renderer (e.g., virtualized list items).
5. Decouple State Implementation from UI
Impact: MEDIUM — Swap state management without changing components.
Define a generic interface for your state shape (value, actions, metadata), then let providers implement it. Components consume the interface, not the implementation.
Define the interface:
Implement with local state:
Swap to API-backed state without changing consumers:
The useCounter() consumers never change.
6. Lift State to Provider Components
Impact: MEDIUM — Enables sibling communication without prop threading.
When two sibling components need shared state, lift it into a provider rather than threading callbacks through the parent.
Avoid — parent threads state to siblings:
Prefer — provider manages shared state:
Both Sidebar and Detail consume useSelection() directly.
7. Use Polymorphic as Props for Flexible Elements
Impact: MEDIUM — One component, any underlying element or component.
The as prop pattern lets consumers control the rendered element while keeping your component's styles and behavior.
Usage:
8. React 19: Drop forwardRef, Use ref as a Prop
Impact: MEDIUM — Simpler component definitions.
React 19 passes ref as a regular prop. No more forwardRef wrapper.
React 18 (deprecated pattern):
React 19:
Similarly, use() can read either promises or context and can be called conditionally:
9. Slot Pattern for Layout Components
Impact: MEDIUM — Named insertion points without render props.
For layout components with multiple content areas, use a slot pattern based on child type detection or named sub-components.
Usage:
10. Headless Components for Maximum Flexibility
Impact: HIGH — Logic without opinions about rendering.
Headless components provide behavior (state, keyboard handling, ARIA attributes) without any markup. Consumers supply the rendering.
Usage — consumer controls all rendering:
Libraries like Radix UI, Headless UI, and React Aria follow this pattern. Prefer them over fully-styled component libraries when you need design flexibility.
Source
Patterns from patterns.dev — composition guidance for the broader React community.


