Posts

The Compound Component Pattern in React: Building Flexible Components

April 25, 2025
When building reusable UI libraries or components in React, it’s essential to provide a clean and intuitive API that developers can use without needing to understand all the internals. That’s where the Compound Component Pattern comes in—a powerful way to build components that work together seamlessly and expose a declarative, readable structure. This pattern is widely used in popular libraries like Radix UI and React Bootstrap. These libraries leverage the Compound Component Pattern to provide flexible and declarative APIs for building accessible and reusable components. This blog will guide you through implementing the Compound Component Pattern with a practical example of a Tabs component system. The Compound Component Pattern allows you to group multiple components under a shared parent while letting them communicate implicitly through context. It’s like <select>, <option>, and <optgroup> in HTML—they work together and make sense as a group. Let’s build a Tabs component system using the compound pattern.
Tabs Usage
<Tabs>
  <Tabs.List>
      <Tabs.Trigger value="account">Account</Tabs.Trigger>
      <Tabs.Trigger value="password">Password</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="account">Account Settings</Tabs.Content>
  <Tabs.Content value="password">Change Password</Tabs.Content>
</Tabs>
Looks clean, right? Let’s make it happen.
TabsContext
// TabsContext.tsx
import { createContext, useContext, use } from 'react';

export const TabsContext = createContext<{
  activeValue: string;
  setActiveValue: (value: string) => void;
} | null>(null);

export const useTabsContext = () => {
  const context = useContext(TabsContext);
  // const context = use(TabsContext);   if using REACT v19
  if (!context) {
    throw new Error('Tabs components must be used within <Tabs>');
  }
  return context;
};
Tabs Container
// Tabs.tsx
import React, { useState } from 'react';
import { TabsContext } from './TabsContext';

const Tabs = ({ children }: { children: React.ReactNode }) => {
const [activeValue, setActiveValue] = useState('');

{/*
If Using REACT v19
return (

<TabsContext value={{ activeValue, setActiveValue }}>
  <div>{children}</div>
</TabsContext>
); */}

return (
<TabsContext.Provider value={{ activeValue, setActiveValue }}>
  <div>{children}</div>
</TabsContext.Provider>
); };

export default Tabs;
TabsTrigger
// TabsTrigger.tsx
import React from 'react';
import { useTabsContext } from './TabsContext';

const TabsTrigger = ({ value, children }: { value: string; children: React.ReactNode }) => {
const { activeValue, setActiveValue } = useTabsContext();

return (
<button
  onClick={() => setActiveValue(value)}
  style={{
    fontWeight: activeValue === value ? "bold" : "normal",
  }}
>
  {children}
</button>
); };

export default TabsTrigger;
TabsList
// TabsList.tsx
const TabsList = ({ children }: { children: React.ReactNode }) => {
  return <div style={{ display: 'flex', gap: '0.5rem' }}>{children}</div>;
};

export default TabsList;
TabsContent
// TabsContent.tsx
import React from 'react';
import { useTabsContext } from './TabsContext';

const TabsContent = ({ value, children }: { value: string; children: React.ReactNode }) => {
const { activeValue } = useTabsContext();

if (value !== activeValue) return null;

return <div style={{ marginTop: '1rem' }}>{children}</div>;
};

export default TabsContent;
Compose API
// index.ts
import Tabs from './Tabs';
import TabsList from './TabsList';
import TabsTrigger from './TabsTrigger';
import TabsContent from './TabsContent';

Tabs.List = TabsList;
Tabs.Trigger = TabsTrigger;
Tabs.Content = TabsContent;

export { Tabs };
Now developers can use your Tabs like this:
Tabs Usage Example
<Tabs>
  <Tabs.List>
      <Tabs.Trigger value="one">One</Tabs.Trigger>
      <Tabs.Trigger value="two">Two</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="one">This is tab one.</Tabs.Content>
  <Tabs.Content value="two">This is tab two.</Tabs.Content>
</Tabs>
  • 🧠 Encapsulation – Components don’t need to pass props around manually.
  • 💡 Clarity – API structure reflects logical grouping.
  • ♻️ Reusability – Easy to split logic and styles across components.
  • 🧩 Flexibility – Easily add nested components like <Tabs.Indicator> or <Tabs.Icon>.
The Compound Component Pattern is a must-know for any React developer building design systems or reusable component APIs. It lets you craft clean, declarative usage while keeping logic under the hood.
On this page