Component composition is building complex UIs by combining smaller, reusable components. Think of it as Lego blocks — small components like buttons, inputs, and cards are snapped together to make bigger components like forms, modals, and dashboards.
Composition is the primary way React encourages you to structure your UI. Props — including the special children prop — are how data and behaviour flow between composed components.
Composition vs. Inheritance
React deliberately prefers composition over inheritance. In object-oriented languages, you might extend a base class to add behaviour — for example, a SpecialButton that extends Button. In React, this approach becomes rigid quickly: deep inheritance chains are hard to follow, and behaviour from a parent class can conflict with what a child needs.
Composition solves this more flexibly. Instead of extending components, you combine them. Each component stays independent and focused on one job, and you assemble them into larger structures through nesting and props. No component needs to know the internals of another.
Basic Composition
function Button({ children, onClick }: { children: string; onClick: () => void }) {
return <button onClick={onClick}>{children}</button>;
}
function Form() {
return (
<form>
<input placeholder="Your name" />
<Button onClick={() => console.log("Submitted")}>Submit</Button>
</form>
);
}
Form composes Button instead of repeating button logic. Each component handles its own concern — Button handles the button behaviour, Form handles the form structure.
The children Prop in Composition
The children prop is the most powerful tool for composition. It allows a component to wrap arbitrary content — making it possible to build container and layout components that don't need to know what's inside them.
function Card({ children }: { children: React.ReactNode }) {
return (
<div className="card">
{children}
</div>
);
}
Using it:
<Card>
<h2>Wariz</h2>
<p>React Developer</p>
</Card>
<Card>
<img src="photo.jpg" alt="Profile" />
<p>Lagos, Nigeria</p>
</Card>
The same Card component wraps completely different content each time. It handles styling and structure — the content is none of its business.
Specialisation
Specialisation is a composition pattern where you create a specific version of a general component by pre-applying certain props. This keeps the base component flexible while providing convenient, named variants for common use cases.
function Button({ label, variant }: { label: string; variant: "primary" | "danger" }) {
return (
<button className={`btn btn-${variant}`}>
{label}
</button>
);
}
// Specialised versions
function PrimaryButton({ label }: { label: string }) {
return <Button label={label} variant="primary" />;
}
function DangerButton({ label }: { label: string }) {
return <Button label={label} variant="danger" />;
}
PrimaryButton and DangerButton are specialisations of Button. You write the core logic once and derive specific variants through composition rather than duplication.
Layout Components
Layout components are a natural extension of composition — their sole job is to arrange their children into a structure. They handle spacing, columns, and positioning while remaining completely agnostic about the content inside them.
function TwoColumnLayout({
left,
right,
}: {
left: React.ReactNode;
right: React.ReactNode;
}) {
return (
<div style={{ display: "flex", gap: "24px" }}>
<div style={{ flex: 1 }}>{left}</div>
<div style={{ flex: 1 }}>{right}</div>
</div>
);
}
Using it:
<TwoColumnLayout
left={<UserProfile />}
right={<ActivityFeed />}
/>
The layout component handles structure. Context and props handle data flow. The two concerns stay separate.
Key Takeaways
- Composition handles structure — how components are arranged and combined.
- Props and context handle data flow — what information components receive.
- Prefer composition over inheritance — it keeps components independent, flexible, and easier to maintain.
- The
childrenprop and named props likeleft/rightare the main tools for building composable layouts.