Composition — The Fundamental Paradigm of Robust UI Components

This article provides an in-depth exploration of component composition, the most potent design technique for developing flexible, reusable UI systems. We'll delve into how slots, children, and compound components facilitate the design of declarative, expressive APIs that scale.

ShareShare

Composition — The Fundamental Paradigm of Robust UI Components

Component composition is the linchpin of a component library's arsenal, furnishing UI libraries with expressiveness, extensibility, and intuitiveness. Composition, unlike the construction of rigid, monolithic components, facilitates breaking down behavior and layout into manageable parts which can be combined and reused across different contexts.

Modern frameworks like React, Vue, and Svelte have composition at their heart. However, to leverage it effectively, particularly in reusable design systems, it's critical to comprehend the underlying architectural strategies.

In this comprehensive guide, we'll explore:

  • The advantage of composition over configuration
  • The functions of children, slots, and compound components
  • Declarative API design
  • Sharing and coordination of context
  • Real-world composition patterns used by libraries like Radix, Material UI, and Headless UI

Understanding Composition in UI

In the context of UI, composition embodies the idea of combining independent parts to create a cohesive whole. More specifically, this implies:

  • Your components are constructed using smaller components
  • These smaller parts can be replaced, rearranged, or excluded
  • The parent component orchestrates the structure, not merely the behavior

Contrast this with a configuration-based approach, often referred to as "prop-drilling":

<Modal size="lg" hasFooter={true} footerContent={<CustomFooter />} />

vs.

<Modal>
  <Modal.Content>
    <p>Hello World</p>
  </Modal.Content>
  <Modal.Footer>
    <CustomFooter />
  </Modal.Footer>
</Modal>

In composition, the intent is made declarative and hierarchical.


The Relevance of Composition

Component composition offers several advantages:

  • Flexibility: Consumers can choose to include or exclude structure
  • Readability: The tree-like structure mirrors the UI layout
  • Separation of concerns: Each part performs its specific task
  • Reusability: Components can be rearranged and used in new contexts
  • Slot-like APIs: Users can inject their own content instead of merely tweaking props

Composition thus transforms components into a language, rather than just widgets.


The Children Prop: Composition 101

React’s children prop is the first step in composition:

<Card>
  <CardHeader />
  <CardBody>Some text</CardBody>
  <CardFooter />
</Card>

Each sub-component might be as simple as:

export const CardHeader = ({ children }) => (
  <div className="card-header">{children}</div>
);

This approach is simple, intuitive, and offers a slot-like mechanism without requiring any additional syntax.


Compound Components

Compound components are a composition pattern where a parent and its children share a common context.

In this pattern, the parent component manages state and logic. In contrast, the child components act as dumb slots that consume the shared context.

<Accordion>
  <AccordionItem>
    <AccordionHeader />
    <AccordionPanel />
  </AccordionItem>
</Accordion>

On a closer look:

  • Accordion provides a context
  • AccordionItem registers itself
  • AccordionHeader toggles visibility
  • AccordionPanel reveals or hides content

This pattern enables intelligent behavior with a declarative structure.


Building Compound Components (React)

Let's look at an example of how to build compound components using React:

const AccordionContext = React.createContext();

function Accordion({ children }) {
  const [openIndex, setOpenIndex] = useState(null);
  return (
    <AccordionContext.Provider value={{ openIndex, setOpenIndex }}>
      {children}
    </AccordionContext.Provider>
  );
}

function AccordionItem({ index, children }) {
  return (
    <AccordionContext.Consumer>
      {({ openIndex, setOpenIndex }) => (
        <div>
          <button onClick={() => setOpenIndex(index)}>Toggle</button>
          {openIndex === index && <div>{children}</div>}
        </div>
      )}
    </AccordionContext.Consumer>
  );
}

This pattern empowers the consumer to structure the layout while the library provides the behavior.


Slot Patterns and Interoperability

In Vue and Web Components, "slots" are first-class citizens. In React, we emulate them using:

  • children
  • props.children
  • Named children (props.title, props.footer)
  • cloneElement or portals

Here is an example:

<Tabs>
  <Tabs.List>
    <Tabs.Trigger>One</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Panel>Content</Tabs.Panel>
</Tabs>

This approach aligns with mental models from markup and design tools. It's not merely a syntactic sugar, but a developer experience enhancer.


Real-World Component Systems

Radix UI

Radix UI fully embraces the concept of compound components:

  • <Dialog />, <DialogTrigger />, <DialogContent />
  • Each part is accessible, minimally styled, and behavior-driven
  • Context handles coordination under the hood

Headless UI (Tailwind)

Tailwind's Headless UI composes behavior via component composition and context

  • Tabs, Listboxes, Switches all work using slot APIs
  • Your UI, their logic

Material UI

Material UI supports composition via:

  • children
  • slots for advanced injection
  • components prop to replace internal elements

Advanced Patterns

  • Controlled + uncontrolled modes via context
  • Scoped slots with context-specific rendering
  • Forwarding references across components
  • Nested composition trees with modular logic boundaries
  • Render-as APIs (e.g., as="a") to customize elements

Anti-Patterns

  • Using composition without isolation which leads to style/logic bleed
  • Relying solely on props to configure deeply nested UIs
  • Hiding too much behavior in parent components without exposing children
  • Neglecting to document which children are valid (composition is a contract!)
  • Overusing cloneElement when context would be simpler

Conclusion: Composition Is the API

Composition is not merely a pattern — it's a philosophy.

It empowers your consumers to define structure while you provide the capability. It allows you to manage complexity without escalating code duplication. And it makes design systems feel like cohesive systems, not just blocks of settings.

Great component APIs are built with composition at their core.

Therefore, don't just build components.

Compose them.

Subscribe for Deep Dives

Get exclusive in-depth technical articles and insights directly to your inbox.

about

Ehsan Hosseini

Ehsan Hosseini

me [at] ehosseini [dot] info

Staff Software Engineer and Tech Lead with a track record of leading high-performing engineering teams and delivering scalable, end-to-end technology solutions. With experience across web, mobile, and backend systems, I help companies make smart architectural decisions, scale efficiently, and align technical strategy with business goals.

© 2025 Ehsan Hosseini. All rights reserved.