new-component

Scaffolds a new React component with its Storybook story, which is the component's test. Use this skill whenever the user asks to "create a component", "make a button", "scaffold a card", "add a new c

SKILL.md

GitHub

new-component

Workflow

  1. Registry check first. Before writing any component by hand, check whether shadcn has it: pnpm shadcn view <name> for a known item, or pnpm shadcn search (for example pnpm shadcn search dialog) to look. A registry counterpart means a button, dialog, tabs, popover, select, table and the like.
  2. If the registry has it, run pnpm shadcn add <name>. The file lands flat in app/components/ui/<name>.tsx and is vendored: do not edit it (see apps/web/.claude/rules/shadcn-ui.md). Then compose the added component in a GAIA component or page rather than hand-writing a replacement; add app/components/ui/tests/<name>.stories.tsx for the new ui file. Stop here unless the user still needs a GAIA component around it.
  3. Only when the registry has no counterpart, or the request is app logic around ui parts: confirm with user via AskUserQuestion: name (PascalCase or kebab-case; the folder is kebab-case), parent dir (default app/components), props (or "none").
  4. Run from the repo root: ./.gaia/cli/gaia scaffold component <Name> [flags] (the package comes from the registry, not the working directory).
  5. Verify: pnpm typecheck clean. Open the new files, sanity-check the props.
  6. If user wants more (variants, conditional rendering, complex children): edit the generated files. The skill does not regenerate. Before hand-editing, read one or two existing components with a similar shape and follow their pattern; style with role tokens and cva for variants (apps/web/.claude/skills/tailwind/SKILL.md).

Adopter notes

  • Registry hooks land in ~/hooks as kebab-case use-*.ts files, which GAIA's layout accepts.
  • The utils alias in components.json is required by the config schema but unused: registry files import cn from the cn package, so shadcn add writes no util file.

Flags

  • --no-story is refused (exit 1, nothing written): the story is the component's test, so a component cannot be scaffolded without one
  • --parent <dir>, non-default parent dir, an existing app/components[/...] folder (e.g. app/components/form) or app/pages/<path> to colocate the component in a page folder. A parent equal to or under app/components/ui, or outside app/components and app/pages, is refused; the layout rule is in apps/web/.claude/rules/coding-guidelines-react.md
  • --props "a:string,b:number", typed props rendered as a Props alias and destructured in the signature. Only top-level commas separate props, so comma-bearing types (Record<K, V>, (a, b) => void, tuples) are supported within a single entry.

The story is the test

The scaffold writes tests/index.stories.tsx with a Default story, a Renders story with a .test() that asserts the region, and no test file. Stories run as tests under @storybook/addon-vitest in headless Chromium (run pnpm install:browsers once), and addon-a11y axe-checks every story, so a story that renders a violation fails with no extra test. Import expect from storybook/test; a .test() receives canvas, args and userEvent in its context. The doctrine (a story is a visual state, a behavior is a .test() on it) is in wiki/decisions/Stories as Tests.md.

With --props, the scaffolder fills representative values (title="title", count={0}, ...) at the story's render site. Replace the placeholders with realistic ones. The scaffolded test is a starting point, not complete evidence: add a .test() per behavior (interactions, state transitions, error paths) and a story per visual state as the component grows, and for an interactive component assert a keyboard path and a focus outcome. How to write stories and tests: apps/web/.claude/skills/tdd-react/references/tests-react.md.