Deft
Ask AI

Search

Search skills, authors and topics

vercel-labs / agent-skills

vercel-composition-patterns

React composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.

Install

npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-composition-patterns
title
Use Compound Components
impact
HIGH
impactDescription
enables flexible composition without prop drilling
tags
composition, compound-components, architecture

Use Compound Components

Structure complex components as compound components with a shared context. Each subcomponent accesses shared state via context, not props. Consumers compose the pieces they need.

Incorrect (monolithic component with render props):

tsx
function Composer({  renderHeader,  renderFooter,  renderActions,  showAttachments,  showFormatting,  showEmojis,}: Props) {  return (    <form>      {renderHeader?.()}      <Input />      {showAttachments && <Attachments />}      {renderFooter ? (        renderFooter()      ) : (        <Footer>          {showFormatting && <Formatting />}          {showEmojis && <Emojis />}          {renderActions?.()}        </Footer>      )}    </form>  )}

Correct (compound components with shared context):

tsx
const ComposerContext = createContext<ComposerContextValue | null>(null)
function ComposerProvider({ children, state, actions, meta }: ProviderProps) {  return (    <ComposerContext value={{ state, actions, meta }}>      {children}    </ComposerContext>  )}
function ComposerFrame({ children }: { children: React.ReactNode }) {  return <form>{children}</form>}
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 compound componentconst Composer = {  Provider: ComposerProvider,  Frame: ComposerFrame,  Input: ComposerInput,  Submit: ComposerSubmit,  Header: ComposerHeader,  Footer: ComposerFooter,  Attachments: ComposerAttachments,  Formatting: ComposerFormatting,  Emojis: ComposerEmojis,}

Usage:

tsx
<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>

Consumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.