---
name: component-library-builder
description: Builds accessible, reusable React components (headless + Tailwind) with every variant and state - hover, focus, active, disabled, loading, error. Ships tests and a Storybook story for each. Use when the user asks for a "component", "button", "input", "modal", "dropdown", "toast" or wants to grow their component library.
---

# Component Library Builder

Ship each component so nobody has to reopen the file. Every state present, every prop typed, no surprises.

## Baseline for every component

- **Headless-first** - logic (state, keyboard, focus) lives in a hook. The styled component composes the hook. Consumers can restyle without forking the logic.
- **Variants + states** - `default | primary | ghost | danger` × `default | hover | focus-visible | active | disabled | loading`. Every combo tested in Storybook.
- **A11y** - semantic HTML first, ARIA only to fill gaps. Focus ring visible on `:focus-visible`, never removed. Screen-reader labels for icon buttons.
- **Composable** - accepts `className` (merged via `cn()`) and forwards refs. Never fights the consumer.
- **Motion** - respects `prefers-reduced-motion`. Default transitions are 150-250ms with easing from tokens.

## File structure per component

```
Button/
  Button.tsx          - the component
  useButton.ts        - headless logic (only if it has state)
  Button.stories.tsx  - Storybook story per variant × state
  Button.test.tsx     - unit + accessibility tests
  index.ts            - barrel export
```

## Test coverage minimum

- Renders in every variant.
- Keyboard navigable (Tab, Enter, Space, Escape, Arrow keys where relevant).
- `axe-core` a11y assertion on default render.
- Loading state disables interaction and announces to screen readers.

## Kickoff prompts

- "Build a Combobox: async options, keyboard nav, WAI-ARIA compliant."
- "Add a `loading` state to `<Button>` with a spinner and preserved width."
- "Extract the state logic of `<Modal>` into `useModal()` so it can be reused."
