--- name: design-component description: Design a UI component spec to the house quality bar — anatomy, variants, sizes, the 8 states, token mapping, and accessibility. Use when the user wants to design or document a component (button, input, tabs, toast, combobox, date picker, modal, etc.) at the spec level before or alongside code. For generating framework code, use design-code. --- # Skill: Design Component > **Step 0 — is the kit here?** This skill reads files from the kit. Check once: > `ls ${CLAUDE_SKILL_DIR}/../../../tokens >/dev/null 2>&1 && echo KIT_OK || echo KIT_MISSING` > On `KIT_MISSING` only the skill folders were installed, which is what > `npx skills add` does. Say so plainly, point the user at > `npx ux-ui-agent-skills init` or the plugin install, and stop. Do not guess the > contents of a file you could not open. Produce a complete component specification matching the project format. ## Steps 1. Read `.claude/rules/components.md` → "Component Quality Bar" (the 8-state table) and "Atomic Design"; the always-on 8-state table is in `CLAUDE.md` → Non-Negotiables. 2. Check if it already exists: `${CLAUDE_SKILL_DIR}/../../../components/atoms.md`, `molecules.md`, `organisms.md`, `templates.md`, `navigation.md`, `feedback.md`, `forms-advanced.md`, `overlays.md`. Match the existing spec format. 3. Pull the ARIA pattern from `${CLAUDE_SKILL_DIR}/../../../accessibility/aria-patterns.md` and contrast/target rules from `${CLAUDE_SKILL_DIR}/../../../accessibility/wcag-checklist.md`. 4. Map every value to tokens (`${CLAUDE_SKILL_DIR}/../../../tokens/*.json`) — sizes via `sizing.json`, states via `states.json`. 5. Apply visual judgment from `${CLAUDE_SKILL_DIR}/../../../taste/design-taste.md` (states, focus, no slop). 6. Optional fast start: `python3 ${CLAUDE_SKILL_DIR}/../../../scripts/scaffold_component.py ""` to emit a stub, then fill it in. ## Output Spec with: anatomy diagram, variants table, sizes table, all 8 applicable states, token mapping, accessibility (role/keyboard/SR), and a note to render via `${CLAUDE_SKILL_DIR}/../../../frameworks/adapter-protocol.md`. ## Accuracy — verify every state, don't assume (mandatory when code is produced) A component is only "correct" when **every variant × state** renders right — not just the resting default. Build a **states harness**: render the component in each applicable state (default, hover, focus, active, disabled, loading `aria-busy`, error `aria-invalid`, selected `aria-pressed`/`aria-selected`) × each variant in one HTML file (see `${CLAUDE_SKILL_DIR}/../../../examples/component-states/button.html`). Then RUN the gates and report their real output (CLAUDE.md → Verification Protocol): - `node ${CLAUDE_SKILL_DIR}/../../../scripts/verify_states.mjs [--dark]` — contrast of every element in default/hover/focus - `node ${CLAUDE_SKILL_DIR}/../../../scripts/axe_audit.mjs [--dark]` — ARIA/role/name/label correctness - `node ${CLAUDE_SKILL_DIR}/../../../scripts/measure_render.mjs [--dark]` — every text element AA - overlays/modals also: `node ${CLAUDE_SKILL_DIR}/../../../scripts/verify_focustrap.mjs --open=` Every state must pass in light AND dark before the component is "done". Never claim a state is correct without a gate proving it. ## Gates prove contrast/a11y — they do NOT prove pixels. RENDER AND LOOK. The contrast/axe gates pass while the UI is still visibly broken: a checkbox that doesn't toggle, a dash sitting at the bottom of its box, a checkmark and an indeterminate dash with mismatched stroke weight, a control that's too heavy. **You must screenshot the harness and inspect it** before claiming done — for every state, and after interaction. Playwright + system Chrome: ```js const b = await chromium.launch({channel:'chrome'}); const p = await b.newPage({deviceScaleFactor:4}); await p.goto('file://'+abs); await p.addStyleTag({content:'*{transition:none!important}'}); await p.mouse.move(2000,2000); // park pointer OFF the component await p.locator('.stack').first().screenshot({path:'/tmp/x.png'}); ``` Read the PNG. Then look for, specifically: - **Functional**: click each interactive element and assert the state actually changed (`await loc.click(); expect(await loc.isChecked())`). A custom control whose overlay box covers the real `` will not toggle unless the box has `pointer-events:none` (or an enclosing `