Free SKILL.md scraped from GitHub. Clone the repo or copy the file directly into your Claude Code skills directory.
npx versuz@latest install event4u-app-agent-config-agent-src-skills-ui-component-architectgit clone https://github.com/event4u-app/agent-config.gitcp agent-config/SKILL.MD ~/.claude/skills/event4u-app-agent-config-agent-src-skills-ui-component-architect/SKILL.md---
name: ui-component-architect
description: "Use when shaping a UI component tree — composition vs inheritance, slot patterns, prop API design, controlled vs uncontrolled, polymorphic — even on 'split this component'."
personas:
- frontend-engineer
source: package
domain: engineering
---
# ui-component-architect
> Decide the **shape** of a component tree before the markup is
> written. Picks composition over inheritance, names the slot
> contract, draws the controlled/uncontrolled axis, and stops
> prop bags from growing into god-components. Stack-agnostic —
> the same lens applies to Blade, Livewire, React, or Vue trees.
> Pair with [`existing-ui-audit`](../existing-ui-audit/SKILL.md)
> first; never invent components that already exist.
## When to use
- A new component / screen is being designed and the boundary
between parent and children is unclear.
- An existing component has > 10 props, conditional rendering
trees nested ≥ 3 deep, or a `variant`-prop with > 4 values.
- A primitive (button, card, dialog) is being added to the design
system and its API will be reused across teams.
- German triggers: "wie schneide ich die Komponente?", "Slots
oder Props?", "controlled oder uncontrolled?".
Do NOT use when:
- The component is a one-off, used in one place, and unlikely to
be reused — over-architecting hurts more than it helps.
- The question is **styling**, not shape — route to
[`tailwind-engineer`](../tailwind-engineer/SKILL.md).
- The audit step has not run — route to
[`existing-ui-audit`](../existing-ui-audit/SKILL.md) first; reusing
beats inventing.
## Procedure
### 1. Inspect prior art, state the responsibility in one sentence
Review existing components in the codebase for the same
responsibility — extend rather than rebuild when a match is found.
If none exists, write the new component's purpose: *"Renders a
labelled input with inline error and hint."* If the sentence has an
"and" joining two unrelated jobs, the component is two components.
Reject the draft and split before continuing.
### 2. Pick composition over inheritance
Rules of thumb:
| Pattern | When |
|---|---|
| Compound components (`Card.Header`, `Card.Body`) | Multiple slots with order semantics |
| Children + named slots | One main child, plus 1–2 optional regions |
| Render props / function-as-children | Caller controls rendering of internal state |
| Polymorphic (`as` prop) | Same shell, different semantic element |
| Inheritance / class-extension | Almost never — last resort for legacy adapters |
Composition trades verbose call-sites for a tiny, stable component.
Inheritance trades short call-sites for ABI fragility.
### 3. Draw the controlled / uncontrolled axis
For every piece of state (open, value, selected, expanded), pick:
- **Controlled** — caller passes value + onChange. Caller owns
state. Use when state must sync across siblings or persist.
- **Uncontrolled** — component owns state internally; caller reads
via ref or onChange callback. Use for ephemeral state local to
the component.
- **Controlled with default** — both APIs supported via
`defaultValue` + optional `value`. The most flexible, also the
most code; reserve for design-system primitives.
Mixing controlled / uncontrolled in the same prop without a
default is the single largest source of "why doesn't my component
update?" tickets.
### 4. Cap the prop API
Prop budget per component:
| Tier | Cap |
|---|---|
| Primitive (Button, Input) | ≤ 6 props + `...rest` to underlying element |
| Composite (Card, Dialog) | ≤ 8 props; prefer slots for variants |
| Page section / feature shell | ≤ 4 props; everything else via context |
Over-budget triggers a refactor: extract a config object, push
state into context, or split into compound parts.
### 5. Name the slot contract
For every slot, document: required vs optional, expected element
type or component, default rendering when absent, accessibility
implications (does the slot become the accessible name?). Slots
without contracts become "stuff a div in there and pray".
## Output format
Return:
1. Responsibility + composition pick — single-sentence purpose, chosen
pattern (compound / slots / render-props / polymorphic) with the
one-line trade-off.
2. State + prop API — controlled / uncontrolled / both per state piece,
prop list with type and purpose, slot inventory with a11y notes.
3. Anti-case list — the combinations the component refuses to support
(the explicit "no" surface that callers can rely on).
Concrete shape:
```
Component: <Name>
Responsibility: <one sentence — reject if "and" joins two jobs>
Pattern: <compound | slots | render-props | polymorphic>
State: value=<controlled|uncontrolled|both>; open=<...>; ...
Props (≤ tier): [name: type — purpose]
Slots: [name: required? default? a11y note]
Children: <count, kind>
Anti-cases: <combinations the component refuses to support>
```
## Gotcha
- `variant` props with > 4 values are usually two components in a
trench coat — split when the rendering branches diverge.
- `as` polymorphism is cheap in TypeScript when typed via generics,
expensive without — the type cost is invisible in plain JS.
- Compound components share state via context; nesting two
compound trees of the same family in one parent silently
crosses contexts — namespace the context per instance or
refuse the nesting.
- "Render props" + memoization fight; if the function changes
every render, the child re-renders too. Stabilize via
`useCallback` or hoist.
## Do NOT
- Do NOT design a component without running
[`existing-ui-audit`](../existing-ui-audit/SKILL.md) first.
Reinventing primitives is the #1 source of design drift.
- Do NOT use inheritance when composition fits — class-extension
hierarchies in UI age badly.
- Do NOT ship a "kitchen-sink" prop (`config={...}`) just to dodge
the prop cap — that hides the API instead of taming it.
- Do NOT push the architecture into the tracker as code AC — output
is a design note for refinement, not implementation steps.