composition-patternsBuild flexible React components using compound components, context providers, and explicit variants to avoid boolean prop proliferation and improve scalability.
Install via ClawdBot CLI:
clawdbot install wpank/composition-patternsBuild flexible, maintainable React components using compound components, context providers, and explicit variants. Avoid boolean prop proliferation.
Composition patterns that scale:
composition, compound components, context, provider, boolean props, variants, react patterns, component architecture, render props, children
Source: Vercel Engineering
npx clawhub@latest install composition-patterns
Avoid boolean prop proliferation. Each boolean doubles possible states.
// BAD: 4 booleans = 16 possible states
<Composer isThread isDMThread isEditing isForwarding />
// GOOD: Explicit variants, clear intent
<ThreadComposer channelId="abc" />
<EditComposer messageId="xyz" />
Structure complex components with shared context. Consumers compose what they need.
const ComposerContext = createContext<ComposerContextValue | null>(null)
// Provider handles state
function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
return (
<ComposerContext value={{ state, actions, meta }}>
{children}
</ComposerContext>
)
}
// Subcomponents access context
function ComposerInput() {
const { state, actions: { update }, meta: { inputRef } } = use(ComposerContext)
return (
<TextInput
ref={inputRef}
value={state.input}
onChangeText={(text) => update(s => ({ ...s, input: text }))}
/>
)
}
function ComposerSubmit() {
const { actions: { submit } } = use(ComposerContext)
return <Button onPress={submit}>Send</Button>
}
// Export as namespace
const Composer = {
Provider: ComposerProvider,
Frame: ComposerFrame,
Input: ComposerInput,
Submit: ComposerSubmit,
Header: ComposerHeader,
Footer: ComposerFooter,
}
Usage:
<Composer.Provider state={state} actions={actions} meta={meta}>
<Composer.Frame>
<Composer.Header />
<Composer.Input />
<Composer.Footer>
<Composer.Formatting />
<Composer.Submit />
</Composer.Footer>
</Composer.Frame>
</Composer.Provider>
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<TextInput>
}
interface ComposerContextValue {
state: ComposerState
actions: ComposerActions
meta: ComposerMeta
}
Same UI, different providers:
// Local state provider
function ForwardMessageProvider({ children }) {
const [state, setState] = useState(initialState)
return (
<ComposerContext value={{
state,
actions: { update: setState, submit: useForwardMessage() },
meta: { inputRef: useRef(null) },
}}>
{children}
</ComposerContext>
)
}
// Global synced state provider
function ChannelProvider({ channelId, children }) {
const { state, update, submit } = useGlobalChannel(channelId)
return (
<ComposerContext value={{
state,
actions: { update, submit },
meta: { inputRef: useRef(null) },
}}>
{children}
</ComposerContext>
)
}
Both work with the same component.
Create named components for each use case instead of boolean modes.
// BAD: What does this render?
<Composer
isThread
isEditing={false}
channelId="abc"
showAttachments
/>
// GOOD: Self-documenting
<ThreadComposer channelId="abc" />
Implementation:
function ThreadComposer({ channelId }: { channelId: string }) {
return (
<ThreadProvider channelId={channelId}>
<Composer.Frame>
<Composer.Input />
<AlsoSendToChannelField channelId={channelId} />
<Composer.Footer>
<Composer.Formatting />
<Composer.Submit />
</Composer.Footer>
</Composer.Frame>
</ThreadProvider>
)
}
function EditComposer({ messageId }: { messageId: string }) {
return (
<EditProvider messageId={messageId}>
<Composer.Frame>
<Composer.Input />
<Composer.Footer>
<Composer.CancelEdit />
<Composer.SaveEdit />
</Composer.Footer>
</Composer.Frame>
</EditProvider>
)
}
Components outside the visual hierarchy can access state via provider.
function ForwardMessageDialog() {
return (
<ForwardMessageProvider>
<Dialog>
{/* Composer UI */}
<Composer.Frame>
<Composer.Input placeholder="Add a message" />
<Composer.Footer>
<Composer.Formatting />
</Composer.Footer>
</Composer.Frame>
{/* Preview OUTSIDE composer but reads its state */}
<MessagePreview />
{/* Actions OUTSIDE composer but can submit */}
<DialogActions>
<CancelButton />
<ForwardButton />
</DialogActions>
</Dialog>
</ForwardMessageProvider>
)
}
// Can access context despite being outside Composer.Frame
function ForwardButton() {
const { actions: { submit } } = use(ComposerContext)
return <Button onPress={submit}>Forward</Button>
}
function MessagePreview() {
const { state } = use(ComposerContext)
return <Preview message={state.input} attachments={state.attachments} />
}
Key insight: Provider boundary matters, not visual nesting.
Use children for composition, render props only when passing data.
// BAD: Render props for structure
<Composer
renderHeader={() => <CustomHeader />}
renderFooter={() => <Formatting />}
renderActions={() => <Submit />}
/>
// GOOD: Children for structure
<Composer.Frame>
<CustomHeader />
<Composer.Input />
<Composer.Footer>
<Formatting />
<Submit />
</Composer.Footer>
</Composer.Frame>
When render props ARE appropriate:
// Passing data to children
<List
data={items}
renderItem={({ item, index }) => <Item item={item} index={index} />}
/>
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 <Composer.Input value={state.input} onChange={...} />
}
// GOOD: State isolated in provider
function ChannelProvider({ channelId, children }) {
const { state, update, submit } = useGlobalChannel(channelId)
return (
<Composer.Provider
state={state}
actions={{ update, submit }}
meta={{ inputRef: useRef(null) }}
>
{children}
</Composer.Provider>
)
}
// UI only knows the interface
function ChannelComposer() {
return (
<Composer.Frame>
<Composer.Input /> {/* Works with any provider */}
<Composer.Submit />
</Composer.Frame>
)
}
| 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 |
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 callbacksGenerated Mar 1, 2026
A team maintains a large React application with components that have accumulated many boolean props, leading to unpredictable states and maintenance challenges. They apply compound components and explicit variants to simplify the API, reduce bugs, and improve developer experience during updates.
A company is creating a reusable React component library for internal or external use, such as a design system. They implement compound components with shared context to offer flexible, composable UIs like dialogs or forms, ensuring consistency and ease of customization across projects.
An e-commerce platform needs a dynamic product configurator where users can customize items with various options. Using lifted state and generic context interfaces, they build a composable UI that allows different parts of the page to interact with the configuration state without tight coupling.
A startup is building a collaborative document or code editor where multiple users can edit simultaneously. They leverage compound components and explicit variants to manage different editing modes, ensuring clear intent and scalable state management across UI elements.
A customer support software provider designs dashboards with complex forms and modals for ticket management. By applying composition patterns, they create reusable compound components for forms and dialogs, reducing code duplication and improving maintainability as features evolve.
Offer a React component library or development toolkit as a subscription service, providing regular updates, support, and integration features. Revenue comes from monthly or annual fees paid by development teams or enterprises adopting the patterns for scalable applications.
Provide expert consulting services to help companies refactor their React codebases or train their teams on composition patterns. Revenue is generated through project-based contracts, workshops, and ongoing support agreements tailored to client needs.
Release the skill as open-source to build community adoption and credibility, then monetize through premium add-ons like advanced components, enterprise support, or integration tools. Revenue streams include one-time purchases or tiered support plans.
π¬ Integration Tip
Start by refactoring a single complex component with many boolean props into a compound component pattern, then gradually apply explicit variants to other parts of the codebase for consistency.
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Expert frontend design guidelines for creating beautiful, modern UIs. Use when building landing pages, dashboards, or any user interface.
Use when building UI with shadcn/ui components, Tailwind CSS layouts, form patterns with react-hook-form and zod, theming, dark mode, sidebar layouts, mobile navigation, or any shadcn component question.
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when building web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Create distinctive, production-grade static sites with React, Tailwind CSS, and shadcn/ui β no mockups needed. Generates bold, memorable designs from plain text requirements with anti-AI-slop aesthetics, mobile-first responsive patterns, and single-file bundling. Use when building landing pages, marketing sites, portfolios, dashboards, or any static web UI. Supports both Vite (pure static) and Next.js (Vercel deploy) workflows.
AI skill for automated UI audits. Evaluate interfaces against proven UX principles for visual hierarchy, accessibility, cognitive load, navigation, and more. Based on Making UX Decisions by Tommy Geoco.