Define the decisions a component owns. Inspect existing components and callers before proposing a duplicate or a universal abstraction.
Work
- Name purpose, consumers, and ownership: product-agnostic primitive, domain component, or layout. Domain states belong near the feature even when reused across several screens.
- Define necessary inputs, events, controlled/uncontrolled behavior, defaults, and composition slots. Prefer explicit states over combinations of contradictory booleans.
- Specify reachable data and interaction states. Separate component behavior from server authorization and persistence responsibilities.
- Describe native semantics, accessible name, keyboard model, focus entry/return, and feedback. Follow the applicable platform pattern; not every control uses the same keys.
- Define content limits, wrapping, responsive behavior, supported themes, and token usage. Do not truncate essential status labels without an equivalent accessible path.
- For a revision, identify caller migrations and backward compatibility before changing the API.
Deliver
A compact contract: purpose/owner, API, meaningful states, interaction, resilience, acceptance. Include types only when they resolve ambiguity. A simple badge does not need a full component RFC.
## Component specification: [Name]
Purpose: Type: primitive | feature | layout Used in: Variants: States: Props: inputs, events, defaults Accessibility: semantics, keyboard, focus Tokens / responsive: Acceptance: [...]
Verify behavior in its real parent, including long content and keyboard interaction. For implementation requests, build the component and run relevant checks. Use `$states` for gaps across a larger flow.
## Worked example
```markdown
## Component specification: ProjectStatus
**Purpose:** Show a project's lifecycle status next to its name in lists and headers.
**Type:** Feature component in the projects feature; composes the existing `Badge` primitive, which stays product-agnostic.
**Used in:** ProjectList, ProjectDetailHeader
**Variants:** `status`: active, paused, completed, archived. Feature code maps each to a label and tone; an unknown server value renders "Unknown status" in the neutral tone.
**States:** Non-interactive: no hover, focus, or pressed state.
**Props:** `status` (required). No children or style overrides, so the label cannot drift from the status.
**Accessibility:** Text carries the meaning and color only reinforces it. The label wraps instead of truncating, so the full status is always visible and announced.
**Tokens / responsive:** Uses the existing status tone roles; each tone pair is contrast-checked in every shipped theme.
**Acceptance:** Every status and an unknown value render the right label and tone; a long translated label wraps inside a narrow list row without clipping.
Gotchas
- Two booleans that cannot both be true (
isPrimary,isDanger) are onevariantvalue in disguise. - A truncated status or error label hides the one word the user needed; meaningful labels wrap.
- A primitive that knows domain values (
active,archived) gets copied per feature; it should take a tone and a label. - Duplicates hide under other names (
Pill,Tag,Chip); search by behavior and callers, not by name. - Hiding a control in the component does not stop the action; name the server check that does.
Boundaries
- Do not use to define token roles or theme pairs; use
$tokensinstead. Missing tokens are not a blocker: name the roles the component needs. - Do not use to find duplicates or drift across the component library; use
$systeminstead. Revising one existing component's contract stays here. - Do not use to audit a screen for missing loading, empty, or error states; use
$statesinstead.