Dynamic Component Composition Patterns Studio
Enterprise-ready skill that automates reusable React component composition and patterns. Built for Claude Code with best practices and real-world patterns.
Component Composition Patterns Studio
Advanced React component composition toolkit covering compound components, render props, headless UI patterns, slot-based composition, and provider patterns for building flexible, reusable component libraries.
When to Use This Skill
Choose Component Composition Patterns when:
- Building reusable component libraries or design systems
- Creating flexible components that work across many use cases
- Refactoring rigid components into composable primitives
- Implementing headless UI components with custom styling
- Designing API surfaces that balance flexibility and simplicity
Consider alternatives when:
- Building one-off components — keep it simple, don't over-abstract
- Using a pre-built library — use their existing composition patterns
- Need performance over flexibility — prefer concrete components
Quick Start
# Activate composition patterns claude skill activate dynamic-component-composition-patterns-studio # Refactor to compound components claude "Refactor the Select component to use compound component pattern" # Build headless component claude "Create a headless Accordion component with full keyboard support"
Example: Compound Component Pattern
// Compound Components — flexible, declarative API import { createContext, useContext, useState, useCallback, ReactNode } from 'react'; // Internal context interface AccordionContextValue { openItems: Set<string>; toggle: (id: string) => void; multiple: boolean; } const AccordionContext = createContext<AccordionContextValue | null>(null); function useAccordion() { const ctx = useContext(AccordionContext); if (!ctx) throw new Error('Accordion components must be used within <Accordion>'); return ctx; } // Root component interface AccordionProps { children: ReactNode; multiple?: boolean; defaultOpen?: string[]; } function Accordion({ children, multiple = false, defaultOpen = [] }: AccordionProps) { const [openItems, setOpenItems] = useState(new Set(defaultOpen)); const toggle = useCallback((id: string) => { setOpenItems(prev => { const next = new Set(multiple ? prev : []); if (prev.has(id)) next.delete(id); else next.add(id); return next; }); }, [multiple]); return ( <AccordionContext.Provider value={{ openItems, toggle, multiple }}> <div role="region">{children}</div> </AccordionContext.Provider> ); } // Child components function AccordionItem({ id, children }: { id: string; children: ReactNode }) { return <div data-accordion-item={id}>{children}</div>; } function AccordionTrigger({ id, children }: { id: string; children: ReactNode }) { const { openItems, toggle } = useAccordion(); const isOpen = openItems.has(id); return ( <button onClick={() => toggle(id)} aria-expanded={isOpen} aria-controls={`panel-${id}`}> {children} <ChevronIcon className={isOpen ? 'rotate-180' : ''} /> </button> ); } function AccordionContent({ id, children }: { id: string; children: ReactNode }) { const { openItems } = useAccordion(); if (!openItems.has(id)) return null; return <div id={`panel-${id}`} role="region">{children}</div>; } // Compose the API Accordion.Item = AccordionItem; Accordion.Trigger = AccordionTrigger; Accordion.Content = AccordionContent; // Usage — clean, declarative, flexible <Accordion multiple defaultOpen={['faq-1']}> <Accordion.Item id="faq-1"> <Accordion.Trigger id="faq-1">What is this?</Accordion.Trigger> <Accordion.Content id="faq-1"> <p>This is a compound component accordion.</p> </Accordion.Content> </Accordion.Item> <Accordion.Item id="faq-2"> <Accordion.Trigger id="faq-2">How does it work?</Accordion.Trigger> <Accordion.Content id="faq-2"> <p>State is shared via context between compound children.</p> </Accordion.Content> </Accordion.Item> </Accordion>
Core Concepts
Composition Patterns
| Pattern | Description | Best For |
|---|---|---|
| Compound Components | Related components share implicit state via context | Accordions, tabs, menus, selects |
| Render Props | Function-as-child passing render control to consumer | Data fetching, animations, state logic |
| Headless Components | Logic hooks without UI, consumer provides markup | Custom-styled design systems |
| Slot Pattern | Named insertion points for content customization | Layouts, cards, dialogs |
| Provider Pattern | Context-based state sharing across tree | Theme, auth, feature flags |
| Higher-Order Components | Functions that wrap components with behavior | Cross-cutting concerns (deprecated in favor of hooks) |
Pattern Selection Guide
| Need | Recommended Pattern | Why |
|---|---|---|
| Multiple related components that share state | Compound Components | Declarative, clean API |
| Reusable logic with custom UI | Headless (hooks) | Maximum flexibility |
| Flexible rendering within a component | Render Props | Consumer controls output |
| Consistent layout with customizable sections | Slot Pattern | Structured flexibility |
| Global state (theme, auth) | Provider Pattern | Tree-wide access |
// Headless Component Pattern — logic hook function useToggle(initial = false) { const [isOn, setIsOn] = useState(initial); const toggle = useCallback(() => setIsOn(prev => !prev), []); const setOn = useCallback(() => setIsOn(true), []); const setOff = useCallback(() => setIsOn(false), []); return { isOn, toggle, setOn, setOff }; } // Consumer provides all UI function FeatureFlag({ featureName }: { featureName: string }) { const { isOn, toggle } = useToggle(false); return ( <div className="flex items-center gap-2"> <span>{featureName}</span> <button onClick={toggle} className={isOn ? 'bg-green-500' : 'bg-gray-300'} role="switch" aria-checked={isOn} > {isOn ? 'ON' : 'OFF'} </button> </div> ); }
Configuration
| Parameter | Description | Default |
|---|---|---|
pattern | Default pattern: compound, headless, render-prop | compound |
typescript | Full TypeScript support with generics | true |
accessibility | Include ARIA attributes and keyboard support | true |
animation | Include animation support hooks | false |
testing | Generate testing utilities alongside components | true |
Best Practices
-
Start with the consumer API, then implement internally — Write example usage code first to design the ideal developer experience, then implement the internals to match. This produces components that are intuitive to use rather than easy to build.
-
Use compound components for related UI elements that share state — When multiple components need to communicate (tabs + panels, select + options, accordion + items), compound components with shared context create the cleanest API.
-
Provide both headless hooks and pre-styled components — Export a
useAccordion()hook for full customization and a styled<Accordion>component for quick usage. This serves both design system consumers (need flexibility) and application developers (need speed). -
Make components controlled and uncontrolled — Support both
value+onChange(controlled) anddefaultValue(uncontrolled) modes. This matches React form conventions and allows consumers to choose their state management approach. -
Include TypeScript generics for type-safe data binding — Components that render lists or handle data should use generics:
<Select<User> items={users} getLabel={u => u.name}>. This propagates type information to event handlers and render functions.
Common Issues
Context-based compound components break when consumers add wrapper elements. Use React's Children.map and cloneElement sparingly — they're fragile. Instead, use context for state sharing so children can be nested at any depth within the provider tree.
Render prop pattern creates deeply nested "callback hell" JSX. When multiple render props stack, readability suffers. Refactor to hooks: convert <Toggle>{(isOn) => ...}</Toggle> to const { isOn } = useToggle(). Hooks compose more cleanly than nested render props.
Headless components require too much boilerplate for simple use cases. Provide pre-composed "batteries-included" variants alongside the headless primitives. Export <Accordion> (styled, pre-composed) and useAccordion() (headless, flexible) from the same package to serve both quick and custom use cases.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.