react-composition

Build flexible component APIs through composition instead of configuration.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-composition" with this command: npx skills add s-hiraoku/synapse-a2a/s-hiraoku-synapse-a2a-react-composition

React Composition

Build flexible component APIs through composition instead of configuration.

Core Principle

Composition over configuration. When a component needs a new behavior, the answer is almost never "add a boolean prop." Instead, compose smaller pieces together.

// BAD: Boolean prop explosion <Modal hasHeader hasFooter hasCloseButton isFullScreen isDismissable hasOverlay centerContent />

// GOOD: Compose what you need <Modal> <Modal.Header> <Modal.Title>Settings</Modal.Title> <Modal.Close /> </Modal.Header> <Modal.Body>...</Modal.Body> <Modal.Footer> <Button onClick={save}>Save</Button> </Modal.Footer> </Modal>

Pattern 1: Compound Components

Share implicit state through context. Each sub-component is independently meaningful.

// 1. Define shared context interface TabsContextValue { activeTab: string; setActiveTab: (tab: string) => void; } const TabsContext = createContext<TabsContextValue | null>(null);

function useTabs() { const ctx = use(TabsContext); // React 19 if (!ctx) throw new Error('useTabs must be used within <Tabs>'); return ctx; }

// 2. Root component owns the state function Tabs({ defaultTab, children }: { defaultTab: string; children: React.ReactNode }) { const [activeTab, setActiveTab] = useState(defaultTab); return ( <TabsContext value={{ activeTab, setActiveTab }}> <div role="tablist">{children}</div> </TabsContext> ); }

// 3. Sub-components consume context function TabTrigger({ value, children }: { value: string; children: React.ReactNode }) { const { activeTab, setActiveTab } = useTabs(); return ( <button role="tab" aria-selected={activeTab === value} onClick={() => setActiveTab(value)} > {children} </button> ); }

function TabContent({ value, children }: { value: string; children: React.ReactNode }) { const { activeTab } = useTabs(); if (activeTab !== value) return null; return <div role="tabpanel">{children}</div>; }

// 4. Attach sub-components Tabs.Trigger = TabTrigger; Tabs.Content = TabContent;

Pattern 2: Explicit Variants

When components have distinct modes, create explicit variant components instead of boolean switches.

// BAD: Boolean modes <Input bordered /> <Input underlined /> <Input ghost />

// GOOD: Explicit variants <Input.Bordered placeholder="Name" /> <Input.Underlined placeholder="Name" /> <Input.Ghost placeholder="Name" />

// Implementation: shared base, variant-specific styles function createInputVariant(className: string) { return forwardRef<HTMLInputElement, InputProps>((props, ref) => ( <InputBase ref={ref} className={cn(className, props.className)} {...props} /> )); }

Input.Bordered = createInputVariant('border border-gray-300 rounded-md px-3 py-2'); Input.Underlined = createInputVariant('border-b border-gray-300 px-1 py-2'); Input.Ghost = createInputVariant('bg-transparent px-3 py-2');

Pattern 3: Children Over Render Props

Use children for composition. Only use render props when the child needs data from the parent.

// BAD: Render prop when children would work <Card renderHeader={() => <h2>Title</h2>} renderBody={() => <p>Content</p>} />

// GOOD: Children composition <Card> <Card.Header><h2>Title</h2></Card.Header> <Card.Body><p>Content</p></Card.Body> </Card>

// ACCEPTABLE: Render prop when child needs parent data <Combobox> {({ isOpen, selectedItem }) => ( <> <Combobox.Input /> {isOpen && <Combobox.Options />} {selectedItem && <Badge>{selectedItem.label}</Badge>} </> )} </Combobox>

Pattern 4: Context Interface Design

Design context interfaces with clear separation of state, actions, and metadata.

interface FormContext<T> { // State (read-only from consumer perspective) values: T; errors: Record<string, string>; touched: Record<string, boolean>;

// Actions (stable references) setValue: (field: keyof T, value: T[keyof T]) => void; setTouched: (field: keyof T) => void; validate: () => boolean; submit: () => Promise<void>;

// Metadata isSubmitting: boolean; isDirty: boolean; isValid: boolean; }

State Lifting

Move state into provider when siblings need access.

// BAD: Prop drilling function Parent() { const [selected, setSelected] = useState<string | null>(null); return ( <> <Sidebar selected={selected} onSelect={setSelected} /> <Detail selected={selected} /> </> ); }

// GOOD: Shared context function Parent() { return ( <SelectionProvider> <Sidebar /> <Detail /> </SelectionProvider> ); }

React 19 APIs

Drop forwardRef

React 19 passes ref as a regular prop.

// Before (React 18) const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => ( <input ref={ref} {...props} /> ));

// After (React 19) function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) { return <input ref={ref} {...props} />; }

use() Instead of useContext()

// Before const ctx = useContext(ThemeContext);

// After (React 19) — works in conditionals and loops const ctx = use(ThemeContext);

Decision Guide

Situation Pattern

Component has 3+ boolean layout props Compound components

Multiple visual modes of same component Explicit variants

Parent data needed in flexible child layout Render prop

Siblings share state Context provider + state lifting

Simple customization of a slot children prop

Component needs imperative API useImperativeHandle

Anti-Patterns

Avoid Why Instead

<Component isX isY isZ />

Combinatorial explosion, unclear interactions Compound components or explicit variants

renderHeader , renderFooter

Couples parent API to child structure children

  • slot components

Deeply nested context providers Performance + debugging nightmare Colocate state with consumers, split contexts

React.cloneElement for injection Fragile, breaks with wrappers Context-based composition

Single mega-context for all state Every consumer re-renders on any change Split into StateContext

  • ActionsContext

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

synapse-a2a

No summary provided by upstream source.

Repository SourceNeeds Review
General

synapse-reinst

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

system-design

No summary provided by upstream source.

Repository SourceNeeds Review