Overview
What is the Compound Component Pattern?
Use Case: Custom Tabs Component
Final Usage:
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>
Step 1: Set Up Context
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;
};
Step 2: Build the Tabs Container
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;
Step 3: Build Subcomponents
Trigger
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;
List Wrapper
TabsList
// TabsList.tsx
const TabsList = ({ children }: { children: React.ReactNode }) => {
return <div style={{ display: 'flex', gap: '0.5rem' }}>{children}</div>;
};
export default TabsList;
Content
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;
Step 4: Compose the Compound Component API
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 };
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>
Why Use the Compound Component Pattern?
- 🧠 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>.