skills/team/react-component-scaffolder/SKILL.md
Scaffolds a single React / TypeScript component or route with a typed props interface, a co-located React Testing Library test, an accessibility baseline, and an optional Storybook story. React analog of fastapi-scaffolder — the component/route is the front-end "unit". Use when creating a new React component, adding a route/page, generating a typed presentational or container component, or standing up a component with its test and story. Triggers on phrases like "scaffold react component", "create react component", "new react component", "add react route", "react page component", "react component with test", "react storybook component".
npx skillsauth add michaelalber/ai-toolkit react-component-scaffolderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
"A component is a contract. Make its props explicit, its output accessible, its behavior tested." -- Adapted from API design practice
"Accessibility by default means the inaccessible path requires more work than the accessible one." -- Adapted from OWASP Secure Design Principles
A React component is the front-end unit the way an endpoint is the back-end unit. Like an endpoint, it has a contract (its props), an output that must be safe and well-formed (accessible, escaped JSX), and a test that proves it behaves. A component shipped without a typed props interface, a test, and an accessibility baseline is a black box that future changes will break silently.
Accessibility by default means semantic HTML and keyboard support are the scaffold, not a follow-up
ticket. The inaccessible <div onClick> must be the path that requires extra justification.
Grounding note: the KB has no React corpus. Use
collection="javascript"for TS,collection="ui_ux"for WCAG/ARIA, and cite react.dev for component/hook patterns. Never invent areactcollection.
Non-Negotiable Constraints:
Props type/interface; no implicit any*.test.tsx is created with the component, not aftereslint-plugin-jsx-a11y cleanWhat this skill is NOT:
react-feature-slice to organize many componentsreact-app-scaffolder to create the project skeleton| # | Principle | Description | Applied As |
|---|-----------|-------------|------------|
| 1 | Explicit Props Contract | Every component declares a Props type. Optional props have defaults; callbacks are typed. | type ButtonProps = { label: string; onClick: () => void; disabled?: boolean } |
| 2 | Presentational vs Container | A presentational component is pure (props in, JSX out). A container wires data/state to a presentational child. Default to presentational. | UserCard (pure) vs UserCardContainer (uses a query hook) |
| 3 | Accessibility Baseline | Semantic element first; interactive elements are real <button>/<a>; images have alt; inputs have labels. | <button type="button"> not <div role="button"> |
| 4 | Controlled by Default | Form inputs are controlled (value + onChange) unless there is a measured reason to use uncontrolled refs. | <input value={value} onChange={…} /> |
| 5 | Composition over Props Explosion | When a component sprouts many boolean props, prefer children/slots or splitting it. | <Card><Card.Header/>…</Card> over 8 show* flags |
| 6 | Stable Identity | Callbacks/objects passed to memoized children are stabilized; list items use stable keys. | useCallback/useMemo only where a memoized child needs it |
| 7 | Test the Behavior | The RTL test renders the component and asserts user-visible behavior via roles/text, not implementation. | screen.getByRole("button", { name: /save/ }) |
| 8 | Story for the Surface | A Storybook story (optional) documents the props surface and edge states (loading, empty, error). | Default, Loading, Empty, Error stories |
| 9 | No Silent any | Props, event handlers, and refs are typed. any requires a written justification. | (e: React.ChangeEvent<HTMLInputElement>) => void |
| 10 | Errors Surface | Async/container components render an error and (where relevant) sit under an error boundary. | if (isError) return <p role="alert">…</p> |
Use search_knowledge (grounded-code-mcp). No React corpus — these cover TS + accessible UI; cite react.dev.
| Query | When to Call |
|-------|--------------|
| search_knowledge("WCAG button link semantics keyboard", collection="ui_ux") | At SCAFFOLD — confirm the accessible element for the interaction |
| search_knowledge("WCAG form label error association", collection="ui_ux") | When the component is a form/input |
| search_knowledge("TypeScript React event handler types props", collection="javascript") | When typing props and handlers |
| search_code_examples("react testing library user event", language="typescript") | When generating the RTL test |
# React + TS + test runner + storybook
grep -E '"(react|typescript|vitest|jest|@testing-library/react|@storybook)"' package.json
# Where components live and the existing convention
find src -type d -name components | head
ls src/features 2>/dev/null
# Test + story file conventions already in use
find src -name "*.test.tsx" | head -3
find src -name "*.stories.tsx" | head -3
Record: React version, test runner (Vitest/Jest), whether Storybook is present, naming/location convention.
Objective: Create the component with its typed props, test, and (optional) story.
See references/component-template.md for full skeletons (presentational, container, form).
src/<location>/<Name>/
<Name>.tsx # the component (presentational by default)
<Name>.test.tsx # RTL test (co-located, required)
<Name>.stories.tsx # Storybook story (optional)
index.ts # re-export
Objective: Meet the a11y baseline. See references/accessibility-baseline.md.
npx eslint src/<location>/<Name> --plugin jsx-a11y
npx tsc --noEmit
npx eslint src/<location>/<Name>
npx vitest run src/<location>/<Name>
# Story renders (if Storybook present):
npx storybook build --quiet >/dev/null 2>&1 && echo "stories build OK"
<react-component-scaffold-state>
phase: DETECT | SCAFFOLD | ACCESSIBILITY | VERIFY | COMPLETE
component_name: [name]
kind: presentational | container | form | route
test_runner: vitest | jest
storybook_present: true | false
props_typed: true | false
test_created: true | false
a11y_clean: true | false | not_run
last_action: [description]
next_action: [description]
</react-component-scaffold-state>
## React Component Scaffold: [Component Name]
### Contract
- [ ] `Props` type/interface declared (no implicit `any`)
- [ ] Optional props have defaults; callbacks typed
- [ ] Kind chosen: presentational / container / form / route
### Accessibility
- [ ] Semantic element used (button/link/heading/list)
- [ ] Labels / `alt` / ARIA where needed
- [ ] Keyboard reachable; focus visible
- [ ] `eslint-plugin-jsx-a11y` clean
### Tests
- [ ] RTL test co-located; asserts behavior via roles/text
- [ ] Edge states covered (empty / loading / error) where relevant
### Story (optional)
- [ ] Default + edge-state stories
### Verification
- [ ] `tsc --noEmit` clean
- [ ] `eslint` clean
- [ ] `vitest` green
anyWRONG:
export function Badge(props) { // implicit any props
return <span>{props.text}</span>;
}
RIGHT:
export type BadgeProps = { text: string; tone?: "info" | "warn" };
export function Badge({ text, tone = "info" }: BadgeProps) {
return <span className={`badge badge--${tone}`}>{text}</span>;
}
WRONG:
<div onClick={onSave}>Save</div> // not keyboard/AT reachable
RIGHT:
<button type="button" onClick={onSave}>Save</button>
The *.test.tsx is created in the same step as the component. A component without a co-located test is
an incomplete scaffold — never defer it.
| # | Anti-Pattern | Why It Fails | Correct Approach |
|---|-------------|-------------|-----------------|
| 1 | Untyped / implicit any props | No compile-time safety; bad autocomplete | Declare an explicit Props type |
| 2 | <div onClick> for actions | Not keyboard/screen-reader reachable | Use <button>/<a> |
| 3 | No co-located test | Behavior unverified; regressions slip in | Create *.test.tsx with the component |
| 4 | Data fetching in a presentational component | Couples UI to transport; hard to test | Fetch in a container/hook; pass data as props |
| 5 | Boolean-prop explosion | Unreadable, combinatorial states | Compose with children/slots or split |
| 6 | Uncontrolled inputs by default | State drifts from the DOM | Controlled inputs (value + onChange) |
| 7 | Testing implementation details | Brittle tests break on refactor | Query by role/text; assert user-visible behavior |
| 8 | key={index} on dynamic lists | Wrong reconciliation | Stable id key |
| 9 | New literals to memoized children | Defeats memo | useMemo/useCallback the value |
| 10 | Missing error/empty states | Blank UI on failure | Render explicit empty/error states |
jsx-a11y flags an interactive elementSymptoms: eslint-plugin-jsx-a11y errors on a clickable div/span.
Recovery:
1. Replace with the semantic element (<button>/<a>).
2. If a non-semantic element is truly required, add role + tabIndex + onKeyDown and document why.
3. Re-run eslint until clean.
Symptoms: getByRole/getByLabelText throws "unable to find".
Recovery:
1. Prefer getByRole with an accessible name — if it is missing, the component has an a11y gap, not just a test gap.
2. Add the label/aria-label to the component (fixes both the test and accessibility).
3. Use screen.debug() to inspect the rendered tree.
| Skill | Relationship |
|-------|-------------|
| react-feature-slice | Provides the feature structure; this skill generates the individual components inside a slice. |
| react-app-scaffolder | Stands up the project (Vite + TS + Vitest + Storybook) this skill scaffolds components into. |
| react-security-review | After scaffolding, audit link/URL handling and any raw-HTML usage in the component. |
| react-architecture-checklist | Quality gate for hooks discipline and render performance across components. |
| tdd | Drive the component test-first (RED → GREEN → REFACTOR) instead of scaffolding code ahead of the test. |
development
Federal / government security overlay applied ON TOP OF a base language security review (dotnet/python/php/rust/react). Language-agnostic: adds NIST SP 800-53 control mapping, FIPS 140-2/3 cryptographic compliance (with a per-language crypto table), CUI handling, EO 14028 supply-chain requirements, and DOE Order 205.1B, and emits POA&M-ready findings with FIPS 199 impact levels. Use for federal/DOE/DOD/national-laboratory systems. Triggers on "federal security review", "NIST compliance", "NIST 800-53", "FISMA", "CUI", "FIPS audit", "DOE security", "POA&M", "ATO review". Do NOT use alone — run the matching <lang>-security-review FIRST; this overlay maps and extends it.
tools
OWASP-based security review of React / TypeScript front-end applications. Detects the framework (Vite/CRA/Next), entry points, and data flows, scans against the OWASP Top 10 (2025) mapped to React client-side patterns (XSS via raw HTML, URL/protocol injection, secrets in the bundle, insecure token storage, dependency CVEs, missing CSP, open redirects), and produces a manager-friendly executive summary plus a graded technical findings table. Use to audit React code for vulnerabilities. Triggers on "react security review", "frontend security audit", "audit react for vulnerabilities", "owasp react", "react xss", "react security posture", "npm audit review". For federal / gov / DOE / NIST / FIPS / CUI context, run security-review-federal after this base review. Do NOT use to grade architecture/structure — use react-architecture-checklist.
tools
Analyzes legacy React codebases and produces actionable modernization plans. Primary migration paths include class components to function components + hooks, Create React App to Vite, React 16/17 to 18 to 19, JavaScript to TypeScript, Enzyme to React Testing Library, legacy Redux to Redux Toolkit / Zustand / Context, and deprecated lifecycle/API removal. Does NOT perform the migration — assesses, quantifies risk, and plans. Triggers on phrases like "modernize react", "class to hooks", "upgrade react", "migrate CRA to vite", "react legacy migration", "react 17 to 18", "react js to typescript", "react technical debt", "enzyme to RTL".
development
Scaffolds feature-based React / TypeScript architecture using feature folders, presentational + container components, custom hooks, a typed data layer, and structural CQRS (query hooks vs mutation hooks). React analog of dotnet-vertical-slice and python-feature-slice — no DI framework; uses props/context for dependency injection and a query cache for server state. Use when creating feature-based React projects, adding React features, organizing components by feature rather than by technical type, or scaffolding a feature's data layer. Triggers on phrases like "scaffold react feature", "create react slice", "react feature folder", "react vertical slice", "add react feature", "react feature architecture", "organize react by feature".