--- id: wiki-2026-0508-compound-components title: Compound Components category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Compound Component Pattern, React Compound Components] duplicate_of: none source_trust_level: A confidence_score: 0.95 verification_status: applied tags: [react, patterns, components, composition, context] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: react --- # Compound Components ## 매 한 줄 > **"매 parent 가 매 child 들과 implicit state share — 매 expressive composable API."**. Compound Component Pattern은 React (그리고 Vue/Solid) 에서 `` 류의 declarative API 를 만드는 표준. Radix UI, Headless UI, Ariakit, shadcn/ui 의 dominant pattern. 2026년 React 19 + Server Components 시대에도 그대로 valid. ## 매 핵심 ### 매 problem solved - 단일 monolithic component 의 props explosion (``). - 매 caller 가 매 children 의 layout/order 를 control 의 X. - Slot composition 의 부재. ### 매 solution - Parent 가 Context 로 shared state 의 publish. - Children 이 Context 를 consume — explicit prop drilling 의 X. - Children 의 component 가 namespace pattern (`Tabs.Tab`) 또는 separate exports. ### 매 patterns - **React Context based** (most common 2026). - **`React.Children` cloning** (legacy, fragile with refs/server components). - **State reducer** (advanced — caller customizes reducer). - **`asChild` slot** (Radix — wraps user element). ### 매 응용 1. Tabs, Accordion, Disclosure. 2. Menu, Dropdown, Combobox. 3. Dialog, Drawer, Popover. 4. Form fields with label/error/description. 5. DataTable column children. ## 💻 패턴 ### Context-based Tabs ```typescript import { createContext, useContext, useId, useState } from 'react'; type TabsContextValue = { activeId: string; setActiveId: (id: string) => void; baseId: string; }; const TabsContext = createContext(null); function useTabs() { const ctx = useContext(TabsContext); if (!ctx) throw new Error('Tabs.* must be used inside '); return ctx; } export function Tabs({ defaultValue, children, }: { defaultValue: string; children: React.ReactNode }) { const [activeId, setActiveId] = useState(defaultValue); const baseId = useId(); return (
{children}
); } function List({ children }: { children: React.ReactNode }) { return
{children}
; } function Trigger({ value, children }: { value: string; children: React.ReactNode }) { const { activeId, setActiveId, baseId } = useTabs(); const selected = activeId === value; return ( ); } function Panel({ value, children }: { value: string; children: React.ReactNode }) { const { activeId, baseId } = useTabs(); if (activeId !== value) return null; return (
{children}
); } Tabs.List = List; Tabs.Trigger = Trigger; Tabs.Panel = Panel; ``` ### Usage ```tsx Overview Settings ``` ### Controlled variant ```typescript type Props = { value?: string; defaultValue?: string; onValueChange?: (value: string) => void; children: React.ReactNode; }; export function Tabs({ value, defaultValue, onValueChange, children }: Props) { const [internal, setInternal] = useState(defaultValue ?? ''); const isControlled = value !== undefined; const activeId = isControlled ? value : internal; const setActiveId = (next: string) => { if (!isControlled) setInternal(next); onValueChange?.(next); }; // ...rest } ``` ### asChild slot pattern (Radix) ```typescript import { Slot } from '@radix-ui/react-slot'; function Trigger({ asChild, children, value }: { asChild?: boolean; value: string; children: React.ReactNode; }) { const { setActiveId } = useTabs(); const Comp = asChild ? Slot : 'button'; return ( setActiveId(value)} role="tab"> {children} ); } // Usage: customize button as anchor Section X ``` ### Form field compound ```typescript const FieldContext = createContext<{ id: string; errorId: string } | null>(null); export function Field({ children }: { children: React.ReactNode }) { const id = useId(); return (
{children}
); } function Label({ children }: { children: React.ReactNode }) { const { id } = useContext(FieldContext)!; return ; } function Input(props: React.InputHTMLAttributes) { const { id, errorId } = useContext(FieldContext)!; return ; } function Error({ children }: { children: React.ReactNode }) { const { errorId } = useContext(FieldContext)!; return {children}; } Field.Label = Label; Field.Input = Input; Field.Error = Error; ``` ### Server Components note ```typescript // Compound components 의 Context — Client Component only. // 매 Server Component layout 에서: // ← Client Component (provides context) // ← Client (consumes context) // ← can be Server Component child // // 'use client'; ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Standard widgets (tabs, menu) | Context-based compound | | Wrap user element | asChild + Slot | | Caller controls state | Controlled variant | | Behavior-only library | Headless compound (Radix style) | | Server Components mixed | 'use client' on the parent + child triggers | **기본값**: Context-based, namespace exports (`Tabs.Trigger`), controlled+uncontrolled support, `useId` for a11y, `useContext` 로 misuse 방지. ## 🔗 Graph - 부모: [[Component-Based Architecture (CBA)]] · [[React Patterns]] - 변형: [[Headless UI]] · [[Render_Props|Render Props]] - 응용: [[Component Library Architecture]] · [[Radix UI]] · [[shadcn-ui]] - Adjacent: [[A11y]] · [[Modern_Web_Rendering_and_Optimization|Server Components]] ## 🤖 LLM 활용 **언제**: building reusable widgets (tabs/menu/dialog), design system primitives, replacing prop-explosion components, headless library. **언제 X**: 1-shot static UI, simple presentational components, server-only components (Context 의 X). ## ❌ 안티패턴 - **`React.Children` cloning**: brittle with refs, breaks with fragments — Context 가 표준. - **Missing context throw**: child used standalone gives undefined error — explicit throw with helpful message. - **Over-namespacing**: 5+ children types — split into multiple components. - **Forgetting a11y**: tabs without role/aria-* — Radix-style attribute mapping 필수. ## 🧪 검증 / 중복 - Verified (Kent C. Dodds compound components / Radix UI source / React 19 docs / WAI-ARIA 1.2). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Context-based Tabs + asChild + form field 패턴 |