/install composition-patterns
React Composition Patterns
Build flexible, maintainable React components using compound components, context providers, and explicit variants. Avoid boolean prop proliferation.
WHAT
Composition patterns that scale:
- Compound components with shared context
- State/actions/meta context interface for dependency injection
- Explicit variant components over boolean props
- Lifted state in provider components
- Children composition over render props
WHEN
- Refactoring components with many boolean props
- Building reusable component libraries
- Designing flexible component APIs
- Creating compound components (Card, Dialog, Form, etc.)
- Components need shared state across sibling elements
KEYWORDS
composition, compound components, context, provider, boolean props, variants, react patterns, component architecture, render props, children
Source: Vercel Engineering
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install composition-patterns
Core Principle
Avoid boolean prop proliferation. Each boolean doubles possible states.
// BAD: 4 booleans = 16 possible states
\x3CComposer isThread isDMThread isEditing isForwarding />
// GOOD: Explicit variants, clear intent
\x3CThreadComposer channelId="abc" />
\x3CEditComposer messageId="xyz" />
Pattern 1: Compound Components
Structure complex components with shared context. Consumers compose what they need.
const ComposerContext = createContext\x3CComposerContextValue | null>(null)
// Provider handles state
function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
return (
\x3CComposerContext value={{ state, actions, meta }}>
{children}
\x3C/ComposerContext>
)
}
// Subcomponents access context
function ComposerInput() {
const { state, actions: { update }, meta: { inputRef } } = use(ComposerContext)
return (
\x3CTextInput
ref={inputRef}
value={state.input}
onChangeText={(text) => update(s => ({ ...s, input: text }))}
/>
)
}
function ComposerSubmit() {
const { actions: { submit } } = use(ComposerContext)
return \x3CButton onPress={submit}>Send\x3C/Button>
}
// Export as namespace
const Composer = {
Provider: ComposerProvider,
Frame: ComposerFrame,
Input: ComposerInput,
Submit: ComposerSubmit,
Header: ComposerHeader,
Footer: ComposerFooter,
}
Usage:
\x3CComposer.Provider state={state} actions={actions} meta={meta}>
\x3CComposer.Frame>
\x3CComposer.Header />
\x3CComposer.Input />
\x3CComposer.Footer>
\x3CComposer.Formatting />
\x3CComposer.Submit />
\x3C/Composer.Footer>
\x3C/Composer.Frame>
\x3C/Composer.Provider>
Pattern 2: Generic Context Interface
Define a contract any provider can implement: state, actions, meta.
interface ComposerState {
input: string
attachments: Attachment[]
isSubmitting: boolean
}
interface ComposerActions {
update: (updater: (state: ComposerState) => ComposerState) => void
submit: () => void
}
interface ComposerMeta {
inputRef: React.RefObject\x3CTextInput>
}
interface ComposerContextValue {
state: ComposerState
actions: ComposerActions
meta: ComposerMeta
}
Same UI, different providers:
// Local state provider
function ForwardMessageProvider({ children }) {
const [state, setState] = useState(initialState)
return (
\x3CComposerContext value={{
state,
actions: { update: setState, submit: useForwardMessage() },
meta: { inputRef: useRef(null) },
}}>
{children}
\x3C/ComposerContext>
)
}
// Global synced state provider
function ChannelProvider({ channelId, children }) {
const { state, update, submit } = useGlobalChannel(channelId)
return (
\x3CComposerContext value={{
state,
actions: { update, submit },
meta: { inputRef: useRef(null) },
}}>
{children}
\x3C/ComposerContext>
)
}
Both work with the same \x3CComposer.Input /> component.
Pattern 3: Explicit Variants
Create named components for each use case instead of boolean modes.
// BAD: What does this render?
\x3CComposer
isThread
isEditing={false}
channelId="abc"
showAttachments
/>
// GOOD: Self-documenting
\x3CThreadComposer channelId="abc" />
Implementation:
function ThreadComposer({ channelId }: { channelId: string }) {
return (
\x3CThreadProvider channelId={channelId}>
\x3CComposer.Frame>
\x3CComposer.Input />
\x3CAlsoSendToChannelField channelId={channelId} />
\x3CComposer.Footer>
\x3CComposer.Formatting />
\x3CComposer.Submit />
\x3C/Composer.Footer>
\x3C/Composer.Frame>
\x3C/ThreadProvider>
)
}
function EditComposer({ messageId }: { messageId: string }) {
return (
\x3CEditProvider messageId={messageId}>
\x3CComposer.Frame>
\x3CComposer.Input />
\x3CComposer.Footer>
\x3CComposer.CancelEdit />
\x3CComposer.SaveEdit />
\x3C/Composer.Footer>
\x3C/Composer.Frame>
\x3C/EditProvider>
)
}
Pattern 4: Lifted State
Components outside the visual hierarchy can access state via provider.
function ForwardMessageDialog() {
return (
\x3CForwardMessageProvider>
\x3CDialog>
{/* Composer UI */}
\x3CComposer.Frame>
\x3CComposer.Input placeholder="Add a message" />
\x3CComposer.Footer>
\x3CComposer.Formatting />
\x3C/Composer.Footer>
\x3C/Composer.Frame>
{/* Preview OUTSIDE composer but reads its state */}
\x3CMessagePreview />
{/* Actions OUTSIDE composer but can submit */}
\x3CDialogActions>
\x3CCancelButton />
\x3CForwardButton />
\x3C/DialogActions>
\x3C/Dialog>
\x3C/ForwardMessageProvider>
)
}
// Can access context despite being outside Composer.Frame
function ForwardButton() {
const { actions: { submit } } = use(ComposerContext)
return \x3CButton onPress={submit}>Forward\x3C/Button>
}
function MessagePreview() {
const { state } = use(ComposerContext)
return \x3CPreview message={state.input} attachments={state.attachments} />
}
Key insight: Provider boundary matters, not visual nesting.
Pattern 5: Children Over Render Props
Use children for composition, render props only when passing data.
// BAD: Render props for structure
\x3CComposer
renderHeader={() => \x3CCustomHeader />}
renderFooter={() => \x3CFormatting />}
renderActions={() => \x3CSubmit />}
/>
// GOOD: Children for structure
\x3CComposer.Frame>
\x3CCustomHeader />
\x3CComposer.Input />
\x3CComposer.Footer>
\x3CFormatting />
\x3CSubmit />
\x3C/Composer.Footer>
\x3C/Composer.Frame>
When render props ARE appropriate:
// Passing data to children
\x3CList
data={items}
renderItem={({ item, index }) => \x3CItem item={item} index={index} />}
/>
Pattern 6: Decouple State from UI
Only the provider knows how state is managed. UI consumes the interface.
// BAD: UI coupled to state implementation
function ChannelComposer({ channelId }) {
const state = useGlobalChannelState(channelId) // Knows about global state
const { submit } = useChannelSync(channelId) // Knows about sync
return \x3CComposer.Input value={state.input} onChange={...} />
}
// GOOD: State isolated in provider
function ChannelProvider({ channelId, children }) {
const { state, update, submit } = useGlobalChannel(channelId)
return (
\x3CComposer.Provider
state={state}
actions={{ update, submit }}
meta={{ inputRef: useRef(null) }}
>
{children}
\x3C/Composer.Provider>
)
}
// UI only knows the interface
function ChannelComposer() {
return (
\x3CComposer.Frame>
\x3CComposer.Input /> {/* Works with any provider */}
\x3CComposer.Submit />
\x3C/Composer.Frame>
)
}
Quick Reference
| Anti-Pattern | Solution |
|---|---|
| Boolean props | Explicit variant components |
| Render props for structure | Children composition |
| State in component | Lift to provider |
| Coupled to state impl | Generic context interface |
| Many conditional renders | Compose pieces explicitly |
Files
rules/architecture-avoid-boolean-props.md- Detailed boolean prop guidancerules/architecture-compound-components.md- Compound component patternrules/state-context-interface.md- Context interface designrules/state-decouple-implementation.md- State isolationrules/state-lift-state.md- Provider patternrules/patterns-explicit-variants.md- Variant componentsrules/patterns-children-over-render-props.md- Composition over callbacks
NEVER
- Add boolean props to customize behavior (use composition)
- Create components with more than 2-3 boolean mode props
- Couple UI components to specific state implementations
- Use render props when children would work
- Trap state inside components when siblings need access
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install composition-patterns - After installation, invoke the skill by name or use
/composition-patterns - Provide required inputs per the skill's parameter spec and get structured output
What is Frontend Composition Patterns?
Build flexible React components using compound components, context providers, and explicit variants to avoid boolean prop proliferation and improve scalability. It is an AI Agent Skill for Claude Code / OpenClaw, with 805 downloads so far.
How do I install Frontend Composition Patterns?
Run "/install composition-patterns" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Frontend Composition Patterns free?
Yes, Frontend Composition Patterns is completely free (open-source). You can download, install and use it at no cost.
Which platforms does Frontend Composition Patterns support?
Frontend Composition Patterns is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created Frontend Composition Patterns?
It is built and maintained by wpank (@wpank); the current version is v1.0.0.