Some time ago I’ve worked on a shared component library for a client which was used in more than three React projects.
So getting the component’s design and API right was crucial for the success of the projects as well as its usage in the other codebases.
In today’s article, I’ll share how we leveraged the Compound Components React Advanced pattern in the development of our React component library.
Why Component Design And Component API Matter?
Crafting a good component design keeps your UI, components, and codebase clear and more maintainable.
A clear and self-descriptive API helps teammates understand how to use the components in the codebase without digging into their implementation details.
When component’s APIs are consistent, it is much easier to swap or extend parts of the application.
Getting your components’s API right in the first place is extremely important since it can set the trajectory of your codebase and application.
What Is Compound Components React Pattern?
The Compound Components pattern is an advanced React Software Design pattern.
It uses a parent component to hold shared logic or state, and child components to render specific parts.
You add children components inside the parent which pick up the needed context or props.
With this pattern you create a cohesive unit which maintains its internal state and acts automatically based on the changed context or state.
You might have seen this pattern if you have used components like <Tabs />, <Accordion />, <Card />, <Dropdown />, etc, where multiple child components depend on each others state.
You have an exposed simple API to use while there’re many things happening under the hood.
Example - <Accordion />
Let’s imagine we have to create an <Accordion /> component:

It should cover the default scenario as well as some other use cases like the ones shown on the screenshot below:

On a first glance, it looks simple, because the component’s API is simple.
It should expose two main properties - **collapseAll** and **showArrow**.
However, when collapseAll is true we must ensure that when we close one of the accordion’s items, the previous opened item is closed automatically.
So the state must be somehow shared between the different accordion items.
Now, let’s dig deeper into the implementation and the code.
Here’s the folder structure and all components.

The main <Accordion /> component which is exposed as a final and single component looks like this:

As you can see, we have four different components and a context to share the internal state between the components.
And here is an example of its usage:
const DemoAccordion = (args: { className?: string; collapseAll?: boolean }) => (
<strong><Accordion </strong>{...args}<strong>></strong>
<strong><Accordion.Item></strong>
<strong><Accordion.Toggle></strong>Item 1<strong></Accordion.Toggle></strong>
<strong><Accordion.Collapse></strong>
<div>Content 1</div>
<strong></Accordion.Collapse></strong>
<strong></Accordion.Item></strong>
<Accordion.Item>
<Accordion.Toggle>Item 2</Accordion.Toggle>
<Accordion.Collapse>
<div>Content 2</div>
</Accordion.Collapse>
</Accordion.Item>
<Accordion.Item>
<Accordion.Toggle>Item 3</Accordion.Toggle>
<Accordion.Collapse>
<div>Content 3</div>
</Accordion.Collapse>
</Accordion.Item>
<strong></Accordion></strong>
);
By using the Compound Components pattern, we managed to create a simple, clear, flexible, and maintainable <Accordion /> component while handling different complex use cases.
Benefits Of Applying Compound Components Pattern In React
- Clear API - components can be read like HTML while nesting shows the relationship between the components.
- No prop drilling - shared state flows through context, not props.
- Flexibility - users can reorder or skip parts without changing the internal logic.
- Reusability - users can mix and match subcomponents across the map.
- Testability - parent and children can be testes separately.
📌 TL;DR
- Good component design and clear components APIs are important for the application’s codebase health.
- Compound Components pattern group related UI pieces under one parent component.
- Context can be used to share state, logic, and callbacks between children.
- Leveraging the Compound Components pattern bring many benefits like no prop drilling, strong reusability and flexibility, and clean code.
