composition-patterns-guide

star 2

Use when refactoring React or React Native components that suffer from boolean prop proliferation, lack of compound structure, prop drilling, or unclear ownership of state. Codifies the 7 Vercel composition-patterns rules adapted to our web + RN stack: avoid boolean props (use composition or explicit variants), prefer compound components with shared context, lift state into providers, use children over render props, modern React 19 patterns (no forwardRef). Also enforces our colocation rules (Rule of Three for promotion, L0/L1/L2 hierarchy, _components/ naming). Triggers on: "refactor this component", "questo componente ha troppi prop booleani", "rendi questo componente più componibile", "compound component", "context provider per X", "design del componente". Not for: scaffolding new components from scratch (use design-md-to-app, screenshot-to-page, rn-add-screen), or moving components up the hierarchy (use promote-component).

lukedj78 By lukedj78 schedule Updated 6/6/2026

name: composition-patterns-guide description: 'Use when refactoring React or React Native components that suffer from boolean prop proliferation, lack of compound structure, prop drilling, or unclear ownership of state. Codifies the 7 Vercel composition-patterns rules adapted to our web + RN stack: avoid boolean props (use composition or explicit variants), prefer compound components with shared context, lift state into providers, use children over render props, modern React 19 patterns (no forwardRef). Also enforces our colocation rules (Rule of Three for promotion, L0/L1/L2 hierarchy, _components/ naming). Triggers on: "refactor this component", "questo componente ha troppi prop booleani", "rendi questo componente più componibile", "compound component", "context provider per X", "design del componente". Not for: scaffolding new components from scratch (use design-md-to-app, screenshot-to-page, rn-add-screen), or moving components up the hierarchy (use promote-component).'

composition-patterns-guide — modern React composition + our colocation rules

This skill is a knowledge guardrail that activates whenever you're designing, refactoring, or reviewing the architecture of a React/RN component. It combines two layers:

  1. The 7 Vercel composition-patterns rules (originally from vercel-labs/agent-skills), adapted to our stack.
  2. Our colocation rules (Rule of Three for promotion, L0/L1/L2, _components/ and components/shared/<dominio>/).

It does NOT execute refactors — for that, use promote-component. It provides the thinking framework.

When this skill applies

  • About to add a 4th boolean prop to an existing component.
  • About to write a component with renderHeader, renderFooter, renderActions render-prop callbacks.
  • A component grew over ~250 lines and needs structural refactor.
  • You see prop drilling 3+ levels deep.
  • About to use forwardRef in new code (React 19+ projects).
  • Reviewing a PR that touches component architecture.

Orchestrator does NOT route here automatically — invoked by the agent's own judgment when the patterns match.

The 7 composition rules (priority order)

Priority 1 — Component Architecture (HIGH)

Rule 1: Avoid boolean prop proliferation

Wrong:

<Composer
  onSubmit={...}
  isThread={true}
  channelId={42}
  isDMThread={false}
  dmId={null}
  isEditing={true}
  isForwarding={false}
/>

Each boolean doubles the state-space. 5 booleans = 32 possible combinations, most of which are invalid or untested.

Right (composition):

<Composer>
  <Composer.Header />
  <Composer.Input />
  <Composer.ThreadField channelId={42} />
  <Composer.EditActions />
  <Composer.Footer onSubmit={...} />
</Composer>

The consumer composes the parts they need. No invalid states.

Rule 2: Use compound components

A complex component should expose subcomponents that share context, not a single API with N props.

Wrong:

function Card({ title, body, footer, showAvatar, avatarUrl }) {
  return <div>...</div>;
}

Right:

function Card({ children }) {
  return <div className="card">{children}</div>;
}
function CardHeader({ children }) { return <div className="card-header">{children}</div>; }
function CardBody({ children }) { return <div className="card-body">{children}</div>; }
Card.Header = CardHeader;
Card.Body = CardBody;
// usage:
<Card>
  <Card.Header><Avatar src="..." /><h2>Title</h2></Card.Header>
  <Card.Body>...</Card.Body>
</Card>

If the subcomponents share state (e.g., a Disclosure that opens/closes), wrap in a context provider.

Priority 2 — State Management (MEDIUM)

Rule 3: Decouple state from implementation

A provider should expose { state, actions, meta } — a generic interface — not surface every state variable as a separate prop.

Wrong:

<DialogProvider isOpen={isOpen} setIsOpen={setIsOpen} title={title} setTitle={setTitle}>

Right:

<DialogProvider value={dialogStore}>  {/* zustand/jotai/use-state hook returns {state, actions, meta} */}

Then consumers use useDialog() and get the same interface regardless of how the state is stored.

Rule 4: Lift state into provider components

When siblings need to share state, move it into a provider that wraps both. Don't drill props through their parent.

Rule 5: Context interface — {state, actions, meta} shape

The provider's context value follows a predictable shape:

type CardContext = {
  state: { isExpanded: boolean };
  actions: { toggle: () => void };
  meta: { id: string };
};

This makes contexts mockable in tests (mockContext({state: {...}, actions: {...}})) and substitutable in Storybook.

Priority 3 — Implementation Patterns (MEDIUM)

Rule 6: Children over render props

Wrong:

<DataTable renderHeader={() => <Header />} renderRow={(row) => <Row data={row} />} />

Right:

<DataTable>
  <DataTable.Header />
  {rows.map(r => <DataTable.Row key={r.id} data={r} />)}
</DataTable>

JSX as children is more readable, more compose-able, and supports tooling (highlighting, refactor, ref forwarding) better than render props.

Rule 7: Explicit variants (sometimes)

For variants that change visual identity significantly, create explicit named exports:

// Instead of:
<Button variant="primary" size="lg">Save</Button>
<Button variant="ghost" size="sm">Cancel</Button>

// Consider:
<PrimaryButton>Save</PrimaryButton>
<GhostButton size="sm">Cancel</GhostButton>

⚠️ Caveat: shadcn / Radix use variant props heavily (via cva) and that's idiomatic in those libraries. The "explicit variants" rule applies more to application-level components than primitives.

Priority 4 — React 19 APIs (MEDIUM, only React 19+)

Rule 8: No forwardRef

In React 19, ref is a normal prop. Don't use forwardRef for new code.

Wrong:

const Button = forwardRef<HTMLButtonElement, Props>((props, ref) => (
  <button ref={ref} {...props} />
));

Right:

function Button({ ref, ...props }: Props & { ref?: Ref<HTMLButtonElement> }) {
  return <button ref={ref} {...props} />;
}

Same for use() instead of useContext():

const ctx = use(MyContext); // React 19+, supports conditional reads

Our colocation rules (briefly — full spec in references/colocation-rules.md)

Level Path Use case
L0 (default) app/<route>/_components/<Name>.tsx Used by 1 page only
L1 app/(group)/_components/<Name>.tsx Used by 2+ pages of same route group
L2 components/shared/<dominio>/<Name>.tsx Used by pages of multiple route groups
UI primitives components/ui/<name>.tsx shadcn/Base UI/MUI primitives
Theme system components/theme/<name>.tsx ThemeProvider, ModeToggle

Rule of Three: stay at L0 until 3rd use, then promote. Use promote-component skill to automate.

React Native specifics

  • Compound components work identically. <Card.Root> + <Card.Header> in NativeWind, no API difference.
  • No forwardRef issue (RN is React 19+ since 2024).
  • Don't lift state to store/ automatically: a Zustand store is for cross-feature state. Within a single feature, context + useState is correct.
  • Compound + NativeWind: each part of a compound can have its own className prop. NativeWind v4 handles the compositions cleanly.

Anti-patterns this skill flags

  • ❌ 4+ boolean props on a single component → suggest compound or explicit variants.
  • renderHeader / renderFooter / renderXxx render props → suggest children or compound.
  • ❌ State managed by parent's useState then drilled through 3+ levels → suggest lifting to provider.
  • forwardRef in React 19+ code → suggest plain ref prop.
  • useContext in React 19+ for conditional access → suggest use().
  • ❌ Compound components split across files at different colocation levels → suggest unifying.
  • ❌ Premature promotion of a component to L2 at the 2nd use → suggest waiting + duplicating.

Sources

Install via CLI
npx skills add https://github.com/lukedj78/dev-flow --skill composition-patterns-guide
Repository Details
star Stars 2
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator