Imported from keystone-ui/react (
.claude/skills/contributing/SKILL.md). Install upstream withnpx skills add keystone-ui/react --skill contributing. Copyright stays with the author.
Component Contributing Workflow
See CONTRIBUTING.md for the full guide.
Adding a New Component
Always start with the scaffolding command:
pnpm add:component MyComponent
This creates all files and updates all registries. Then follow the Storybook-first workflow:
- Implement the component in
packages/ui/src/{name}.tsx - Write Storybook stories in
apps/storybook/stories/{name}.stories.tsx(primary dev surface) - Create Fumadocs demos in
apps/docs/demos/{name}/(3-6 simplified demos from stories) - Register demos in
apps/docs/demos/index.ts - Write MDX docs in
apps/docs/content/docs/components/{name}.mdxusing<ComponentPreview name="{name}-{example}" />. For compound components (≥2 exported parts that nest), include a## CompositionASCII tree after## Usage— see Composition tree below. - LLMs.txt updates automatically (no manual step)
- Sync the AI agent surface — see Sync the AI Agent Surface below
Adding a New Block
Blocks are full-page compositions, hand-registered (no pnpm add:block scaffolder yet). Workflow:
- Author the source under
apps/docs/demos/blocks/<name>.tsx(single-file) orapps/docs/demos/blocks/<name>/(multi-file withindex.tsxre-export). - Register the demo in
apps/docs/demos/index.tsas"block-<name>": { component: ..., file: "blocks/<name>.tsx" }. Theblock-<name>key is what<BlockPreview name="block-<name>">resolves against. - Generate the installable copy with
pnpm sync:blocks. It rewrites the demo's imports (@keystoneui/react/x→@/components/ui/x,@keystoneui/react/utils→@/lib/utils,./sibling→@/components/sibling, de-aliased lucide names) intoregistry/default/blocks/<name>/components/, then formats the result. Never hand-edit those files —pnpm lint:docsruns--checkand fails when they drift from the demo.page.tsxis the exception: four hand-written lines, not generated. - Add the registry entry by hand-editing
registry.json. The block item must include:name,type: "registry:block",title,descriptiondependencies(npm packages) andregistryDependencies(other registry items)files[]with per-file types:registry:page(withtarget: "app/<slug>/page.tsx") for the page entry;registry:componentfor everything elsecategories: [...]— see the taxonomy below.pnpm sync:registrywarns if missing.
- Write the MDX at
apps/docs/content/docs/blocks/<name>.mdx. Required structure: install bash fence,<BlockPreview name="block-<name>">,## Components Usedwith links to/docs/components/<primitive>. The lint atpnpm lint:docsblocks merge if any of these are missing or malformed. Seeapps/docs/content/docs/blocks/_block-template.mdx. - Regenerate backlinks with
pnpm docs:backlinks— this auto-injects## Related Blocksinto every component MDX referenced under## Components Used(idempotent, fenced). - Rebuild the registry with
pnpm registry:build.
Block category taxonomy
Every block must carry at least one category. Current values in use:
| Category | Used by | Mirrors shadcn? |
|---|---|---|
authentication |
sign-in + sign-up blocks | ✅ |
login |
signin-0X (alongside authentication) |
✅ |
signup |
signup-0X (alongside authentication) |
✅ |
navigation |
profile-dropdown-01 |
new |
data |
tickets-01 |
new |
dashboard |
dashboard-01 |
new |
admin |
admin-01 |
new |
betting |
betting-panel-0X |
new |
When adding a new category, prefer single-word lowercase strings that mirror shadcn naming where any overlap exists (shadcn's own registry.json, at https://ui.shadcn.com/r/registry.json, is the reference). Document the new category in this table and in AGENTS.md → "Block Authoring".
Updating an Existing Component
API change (new/renamed prop, new sub-component)
- Update component source
- Update Storybook stories
- Update Fumadocs demos if user-facing
- Update MDX API Reference table
- If a sub-component was added/removed/renamed: update the
## CompositionASCII tree in the MDX - Update
_registry.tsif dependencies changed - Sync the AI agent surface (below) — if the change affects rules, conventions, or the public API the skill teaches
Visual/styling change
- Update component source only
- Storybook and Fumadocs demos auto-reflect
- No agent-surface changes needed unless the styling change introduces a new rule (e.g., new transition, new token)
Description/copy change
- Update MDX frontmatter or section text only
- LLMs.txt auto-reflects on next deploy
Sync the AI Agent Surface
Three surfaces drift if not maintained explicitly. Run through this checklist after any component add or convention change.
When adding a brand-new component
| Surface | Action |
|---|---|
packages/ui/src/_registry.ts |
Add the component entry (handled by pnpm add:component). |
packages/ui/package.json exports |
Add ./{name} subpath in both exports and publishConfig.exports — there are two maps, and a manual add that misses the second ships a broken package. pnpm add:component handles both. |
packages/ui/tsup.config.ts entryPoints |
Add the source path. |
skills/keystoneui-react/SKILL.md |
Bump the exact component count at the Component List heading and add the kebab-case name to the list. |
skills/keystoneui-react/SKILL.md Component Selection table |
Add the component to the appropriate row (e.g. "Form layout" / "Overlays" / "Feedback"). |
apps/docs/content/docs/(getting-started)/agents/mcp-server.mdx |
Bump the "all 54+ UI components" count if you reference one. |
| Component counts elsewhere | Do not bump them. Every other surface says 50+ on purpose. |
Count policy. The component count is hardcoded in ~12 places. Only two state an exact number and both must be updated:
-
skills/keystoneui-react/SKILL.md— the## Component Listheading. -
apps/docs/content/docs/(getting-started)/rtl.mdx— a ratio ("30 of its 56 components"), so recompute it rather than incrementing:ls packages/ui/src/*.tsx | grep -v '.test.tsx' | wc -l # components grep -rlE '\b(ml-|mr-|pl-|pr-|left-|right-)' packages/ui/src/*.tsx \ | grep -v test | wc -l # with physical utilities
Everywhere else — README.md, the home page, the gallery, index.mdx,
llms-utils.ts, mcp-server.mdx, the intro blog post — says 50+ and should
stay that way. Bumping a dozen numbers per component is how they end up
disagreeing with each other, which is exactly the state this replaced (three
different values across ten files).
| apps/docs/app/gallery/page.tsx | Add a {name, slug, description} entry. Hardcoded, so a new component is invisible in the gallery until it is listed. |
| apps/docs/public/r/ | Run pnpm registry:build to rebuild registry artifacts. |
When changing a convention or adding a new rule
| Trigger | Surface to update |
|---|---|
New CSS token (e.g. another --z-*, --ease-*) |
AGENTS.md "Styling Conventions" bullet AND skills/keystoneui-react/rules/styling.md AND .claude/skills/design-tokens/SKILL.md. All three. |
| New form pattern (e.g. new Field variant) | skills/keystoneui-react/rules/forms.md AND any block demos that should reflect it. |
New compound component pattern (e.g. new data-slot use) |
skills/keystoneui-react/rules/composition.md AND .claude/skills/component-architecture/SKILL.md. |
| New popup behavior (height, animation, z-stack) | skills/keystoneui-react/rules/composition.md AND .claude/skills/popup-patterns/SKILL.md. |
| New data-table convention (filter toolbar, applied state, sort affordance, pagination footer, mobile fold) | skills/keystoneui-react/rules/data-tables.md AND .claude/skills/data-table-patterns/SKILL.md AND the Notes section of every block that already does it differently. The shipped rule is the consumer-facing half; the project skill keeps the repo-only material (guard discipline, e2e paths). |
| New install path or CLI flow | skills/keystoneui-react/cli.md AND apps/docs/content/docs/(getting-started)/agents/skills.mdx. |
| New MCP tool | packages/keystoneui-mcp/src/{server,tools,fetcher}.ts AND skills/keystoneui-react/mcp.md AND apps/docs/content/docs/(getting-started)/agents/mcp-server.mdx. |
| New block | Follow Adding a New Block above; then add the block to the Block Selection table in skills/keystoneui-react/SKILL.md, run pnpm docs:backlinks and pnpm registry:build. |
| New block category | AGENTS.md "Block Authoring" taxonomy AND the table in Adding a New Block above AND the Block Selection table in skills/keystoneui-react/SKILL.md. |
| New story or demo variant | Run pnpm lint:stories-demos. If the lint flags it as story-only or demo-only and that's intentional (per-variant story vs consolidated variants.tsx demo, dev-only fixture, etc.), add the entry to scripts/stories-demos-allowlist.json with a one-line rationale in the _notes block. Otherwise author the missing counterpart on the other surface. |
Validation
pnpm --filter @keystoneui/mcp build— confirms MCP changes compile.pnpm --filter @keystoneui/docs build(ortsc --noEmit) — confirms docs MDX/routes are valid.pnpm registry:build— refreshesapps/docs/public/r/*.json.pnpm build:skills— re-tarballsskills/keystoneui-react/andskills/{others}forkeystoneui.io/skills/{name}.tar.gz.
Why this matters
Drift between component source and the skill / AGENTS.md / MCP registry is the single biggest reliability risk for AI consumers. The 6adc8c2 commit is the canonical example: motion/layering tokens were added to AGENTS.md but not to SKILL.md, leaving the skill stale until manual reflow. Treat the agent surface as code — synced together, not as an afterthought.
The eval suite at skills/keystoneui-react/evals/evals.json exists to catch this kind of drift; run it (or extend it) when you change a rule.
Demo File Pattern
Every demo must follow this exact pattern:
"use client";
import { MyComponent } from "@keystoneui/react/my-component";
export default function MyComponentDefault() {
return <MyComponent>Content</MyComponent>;
}
Demo Registry Pattern
In apps/docs/demos/index.ts:
import MyComponentDefault from "./my-component/default";
// In the demos record:
"my-component-default": { component: MyComponentDefault, file: "my-component/default.tsx" },
MDX Pattern
In apps/docs/content/docs/components/my-component.mdx:
<ComponentPreview name="my-component-default" />
Composition tree
For compound components (≥2 exported parts that nest), insert a ## Composition section after ## Usage with an ASCII tree showing the canonical nesting. Mirrors shadcn/ui's pattern — flows through llms-full.txt automatically since it's plain markdown. Skip for single-piece components (Button, Badge, Input, Spinner, etc.).
## Composition
\`\`\`text
MyComponent
└── MyComponentItem
├── MyComponentTrigger
└── MyComponentContent
\`\`\`
Authoring rules:
- Use box-drawing characters (
├──,└──,│) — copy from an existing MDX if unsure. - Indent two spaces per level.
- Order children top-to-bottom in the order they appear in the canonical default demo.
- Show one branch per repeating part (don't list two
MyComponentItems). - Include conventional non-exported nodes when they appear in the standard demo (e.g.
IconunderAlert).
Key File Locations
Component sources
- Component:
packages/ui/src/{name}.tsx - Exports:
packages/ui/package.json - Build entries:
packages/ui/tsup.config.ts - Registry:
packages/ui/src/_registry.ts - Stories:
apps/storybook/stories/{name}.stories.tsx - Demos:
apps/docs/demos/{name}/ - Demo registry:
apps/docs/demos/index.ts - MDX docs:
apps/docs/content/docs/components/{name}.mdx - Component nav:
apps/docs/content/docs/components/meta.json
AI agent surfaces
- Skill (consumers):
skills/keystoneui-react/SKILL.md+mcp.md,cli.md,customization.md,rules/*.md - Skill evals:
skills/keystoneui-react/evals/evals.json - AGENTS.md (root):
/AGENTS.md - AGENTS.md route:
apps/docs/app/agents-md/route.ts - LLMs.txt routes:
apps/docs/app/llms{,-full,-components}.txt/route.ts - MCP server:
packages/keystoneui-mcp/src/{server,tools,fetcher,config}.ts - MCP docs:
apps/docs/content/docs/(getting-started)/agents/{mcp-server,skills,llms-txt,agents-md}.mdx
Project-internal Claude/Cursor skills
.claude/skills/{component-architecture,design-tokens,popup-patterns,data-table-patterns,contributing}/SKILL.md.cursor/rules/*.mdc(symlinks to the same canonical files)