In React, reusable UI usually follows two patterns: composition components and compound components. They are closely related, but they differ mainly in how state is handled and how the API is exposed.
Composition Components (no Context)
This is the most straightforward approach. You build UI by combining components and passing data through props.
Everything is explicit: each component receives what it needs directly from its parent.
This makes the system very flexible and easy to reuse, since every part is independent. You can take a subcomponent and use it anywhere without constraints.
The downside is that as the UI grows, you often end up with prop drilling and a lot of wiring code. Sharing behavior between related parts also becomes harder because there is no shared state mechanism.
In short: simple, explicit, and flexible, but can become verbose in complex UIs.
import Airbnb from "./Airbnb";
<Airbnb.Card>
<Airbnb.ImageWrapper>
<Airbnb.Image src={el.image} alt={el.title} />
<Airbnb.Heart like={el.isFavorite} onClick={() => {}} />
<Airbnb.Recomendation />
</Airbnb.ImageWrapper>
<Airbnb.Content>
<Airbnb.Title title={el.title}>
<Airbnb.Valoration average={el.average} reviews={el.reviews} />
</Airbnb.Title>
<Airbnb.Description description={el.description} />
<Airbnb.Price price={el.price} nights={el.nights} />
<Airbnb.Cancelation />
</Airbnb.Content>
</Airbnb.Card>;
๐ Try it in practice: Composition Components Challenge
Compound Components (with Context)
This pattern introduces shared state using React Context. Instead of passing props through every level, a parent component provides state and child components consume it directly.
This creates a tighter relationship between components. From the outside, the API becomes very clean because users donโt need to manage all the props manually.
It works especially well for structured UI systems where components are meant to work together (like cards, modals, tabs, etc.).
The trade-off is that the relationship becomes implicit. Subcomponents depend on a parent provider and are not really meant to be used in isolation. Debugging can also be slightly less obvious because the data flow is hidden inside Context.
In short: more structured, cleaner API, but less flexible and more coupled.
import Airbnb from "./Airbnb";
<Airbnb.Card data={el}>
<Airbnb.ImageWrapper>
<Airbnb.Image />
<Airbnb.Heart />
<Airbnb.Recomendation />
</Airbnb.ImageWrapper>
<Airbnb.Content>
<Airbnb.Title>
<Airbnb.Valoration />
</Airbnb.Title>
<Airbnb.Description />
<Airbnb.Price />
<Airbnb.Cancelation />
</Airbnb.Content>
</Airbnb.Card>;
๐ Try it in practice: Compound Components Challenge
Export styles
There are two common ways to expose these components.
Named exports give maximum flexibility and are easy to tree-shake, but they donโt communicate relationships between components very well.
import { Card, ImageWrapper, Image } from "./Airbnb";
<Card data={el}>
<ImageWrapper>
<Image />
</ImageWrapper>
</Card>;
Namespaced exports group everything under a single object, which makes the structure much clearer and is often preferred for compound components or design systems. The trade-off is slightly more verbosity and sometimes less optimal tree-shaking.
import Airbnb from "./Airbnb";
<Airbnb.Card data={el}>
<Airbnb.ImageWrapper>
<Airbnb.Image />
</Airbnb.ImageWrapper>
</Airbnb.Card>;
When to use each
Use composition when components are independent and you want full flexibility with explicit data flow.
Use compound components when the UI pieces are strongly related, share state, and benefit from a controlled and clean API.
Summary
Composition is about flexibility and explicitness.
Compound components are about structure and shared state.
Both are just different ways of organizing the same idea: composition in React.
Top comments (0)