# Compound Parts Over a Mega-Component

**SHOULD** · **ID:** `design-compound-over-mega-component` · **Category:** design
**Source:** [Composition Patterns](https://github.com/vercel-labs/agent-skills/tree/main/skills/composition-patterns)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/design-compound-over-mega-component

> SHOULD: Expose components as composable compound parts (Parent owns state/context; Parent.Trigger, Parent.Content, Parent.Item are children) rather than one component with a dozen configuration props. New needs become children, not new props and internal branches. Reserve flat single-prop APIs for atomic things with no inside.

Expose a component as composable parts (Select.Trigger / Select.Content) rather than one component with a dozen configuration props

A component that starts as `<Select options value onChange>` grows a new prop every time a caller needs something slightly different — a label, an icon, a sticky header, a custom empty state — until it is a maze of props and internal conditionals that no one can safely change. Compound components invert this: the parent owns state and context, and the caller composes the pieces (`<Select><SelectTrigger/><SelectContent><SelectItem/></SelectContent></Select>`). New requirements become new children, not new props, so the component stays closed for modification but open for extension. This is the pattern shadcn/ui and Radix are built on, and it is why they rarely need a breaking change to support a new layout. Reserve the flat single-prop API for genuinely atomic things (an icon, a badge) that have no inside.

## Rule snippet

```tsx
<Select value={v} onChange={setV}>
  <SelectTrigger />
  <SelectContent><SelectItem value="a">A</SelectItem></SelectContent>
</Select>
```

## Bad — do not do this

`design-compound-over-mega-component-bad`

```tsx
export function CompoundOverMegaComponentBad() {
  return (
    <div className="w-full max-w-md py-6">
      <pre className="overflow-x-auto rounded-lg bg-muted p-3 text-xs leading-relaxed text-foreground">
        {`<Select
  label="Fruit"
  options={fruits}
  value={value}
  onChange={setValue}
  placeholder="Pick one"
  size="sm"
  error={error}
  renderOption={renderFruit}
  icon={<ChevronDown />}
/>`}
      </pre>
      <p className="mt-4 text-xs text-destructive">
        One component, ten props: every new need — a header, a divider, a custom row — means another prop and another branch inside.
      </p>
    </div>
  );
}
```

## Good — do this

`design-compound-over-mega-component-good`

```tsx
export function CompoundOverMegaComponentGood() {
  return (
    <div className="w-full max-w-md py-6">
      <pre className="overflow-x-auto rounded-lg bg-muted p-3 text-xs leading-relaxed text-foreground">
        {`<Select value={value} onChange={setValue}>
  <SelectTrigger placeholder="Pick one" />
  <SelectContent>
    <SelectLabel>Fruit</SelectLabel>
    <SelectItem value="apple">Apple</SelectItem>
    <SelectItem value="pear">Pear</SelectItem>
  </SelectContent>
</Select>`}
      </pre>
      <p className="mt-4 text-xs text-success">
        Compound parts compose: add a label, divider, or custom row as children — no new prop, no branch in the parent.
      </p>
    </div>
  );
}
```

## References

- [Vercel — composition-patterns skill](https://github.com/vercel-labs/agent-skills/tree/main/skills/composition-patterns)
- [Radix — Composition](https://www.radix-ui.com/primitives/docs/guides/composition)
