This file provides instructions for Claude when working on the Wonder Blocks design system codebase.
- Language: TypeScript (Strict mode enforced)
- Framework: React (Functional Components and Hooks)
- Styling: Aphrodite (
aphrodite) for CSS-in-JS with Wonder Blocks tokens (@khanacademy/wonder-blocks-tokens) - State Management: Local state (
useState) and React Context API (useContext) - Data Fetching: There should be no data fetching in the design system UI components
- Routing: Design system components with link functionality should also support React-Router routes
- Testing: Jest, React Testing Library (RTL),
@testing-library/user-event, Storybook - Package Manager: pnpm
- Use kebab-case for files and directories (e.g.,
activity-button.tsx,utils.ts) - Test files:
*.test.ts(x) - Storybook files:
*.stories.tsx - PascalCase for React components and types (e.g.,
Button,type ButtonProps) - camelCase for functions, variables, hooks (e.g.,
getButtonProps,useButtonFunctionality)
- Use
strictmode - Define clear interfaces/types. Use
typefor props and state,interfacefor shared structures where appropriate - Use utility types (
Partial,Omit,Pick,Readonly, etc.) - Use the
satisfiesoperator for type-safe object literals - Prefer type-only imports:
import type {...}for types - Avoid
any; useunknownor specific types instead
- Imports: Always use
import * as React from "react"(required for JSX transformation) - Use functional components and hooks (
useState,useEffect,useContext,useCallback,useMemo) - Define explicit
Propstypes with object destructuring. Pass complex objects/callbacks with stable references (useCallback,useMemo) if they are dependencies of effects or memoized children - Keep component state minimal. Lift state up when necessary
- Provide stable
keyprops for lists (use item IDs if available) - Build complex UI by composing smaller Wonder Blocks when possible
- Use
React.forwardRefwhen components need to expose DOM refs - Extract reusable logic into custom hooks (e.g.,
useFieldValidation,useIsMountedfrom@khanacademy/wonder-blocks-core) - Event Handlers: Internal handlers prefixed with
handle(e.g.,handleClick), callback props prefixed withon(e.g.,onClick) - Don't use
React.FC<Props>, use(props: Props) =>instead - Avoid creating new class components
Class to Functional Migration: When converting class components to functional components, if there is a componentWillUnmount method, the corresponding useEffect should either not have any dependencies or should call the isMounted function returned by useIsMounted from @khanacademy/wonder-blocks-core.
- Define styles using
StyleSheet.createfromaphrodite. Colocate styles with the component - Use
addStylefrom@khanacademy/wonder-blocks-coreto create styled HTML elements - Use semantic color tokens from
@khanacademy/wonder-blocks-tokens(e.g.,semanticColor.core.background.base) - Use the
focusStylesutility from@khanacademy/wonder-blocks-stylesfor focus indicators - Avoid using primitive
colortokens; usesemanticColortokens instead
- Use
Viewfrom@khanacademy/wonder-blocks-corefor layout containers instead of plain divs - Use React's
useIdhook to generate unique ids (not the deprecatedIdcomponent) - Use
HeadingandBodyTextfrom@khanacademy/wonder-blocks-typographyfor text - Import Phosphor icons from
@phosphor-icons/core(e.g.,import plusIcon from "@phosphor-icons/core/regular/plus.svg") and usePhosphorIconfrom@khanacademy/wonder-blocks-icon - Avoid deprecated components like
Strut
- Organize imports: React, third-party libs, internal absolute paths (
@khan/,@khanacademy/), relative paths (./,../) - Use absolute paths for cross-package imports
- Strictly adhere to ESLint and Prettier
- The project enforces import order: React, third-party libs, then internal imports
- JSDoc comments should be used for complex functions, but TypeScript types are preferred over JSDoc type annotations
- Never remove existing comments that are used to provide context
- Run
pnpm lintbefore submitting changes
- Configuration (Preferred): Components accept props that control rendering
- Example:
ButtonhasstartIconandendIconprops rather than children - Use when: Precise control over styling, positioning, or behavior of child elements
- Example:
- Composition: Components accept other components as children
- Example:
SingleSelectwithOptionItemcomponents as children - Use when: Component contains many similar items or flexibility in structure is needed
- Example:
- Controlled: Parent passes
valueandonChangeprops (e.g.,TextField,TextArea) - Uncontrolled: State lives in the component itself (e.g.,
Accordion) - Both: Support both by making
valueandonChangeoptional (e.g.,Modal,Popover)
General Props:
id?: string- Unique identifier (auto-generated withuseIdif not provided)testId?: string- Test ID for e2e testingref?: React.Ref<T>- Reference to DOM element (useReact.forwardRef)kind?: string- Variant type (e.g.,'primary' | 'secondary' | 'tertiary')value?: T- The value of the component (for form components)disabled?: boolean- Disabled stateautoFocus?: boolean- Focus on page loadlabels?: CustomLabelsType- Custom labels for i18ninitialFocusRef?: Ref | null- Element to receive initial focus (prefer refs over element ids)
ARIA-related Props:
- If a component supports ARIA props, include
AriaPropstype with component props AriaPropsincludes:role,aria-label,aria-labelledby,aria-describedby, etc.- Examples:
Breadcrumbs,TextField
Styling Props:
style?: StyleType- Custom styles for root elementstyles?: {root?, icon?}- Custom styles for multiple elementssize?: SizeUnion- Component size (e.g.,'small' | 'medium' | 'large')animated?: boolean- Enable animations (defaults tofalse)icon?: ReactElement- Supports both PhosphorIcon and Icon components
Event Handler Props:
onChange?: (value: T) => void- Value change (uses value, not event)onClick,onKeyDown,onKeyUp,onFocus,onBlur- Standard React event handlers with appropriate event types
Validation Props (Form Components):
validate?: (value: T) => string | null | void- Returns error message or nullonValidate?: (errorMessage: string | null) => void- Validation callbackerror?: boolean- Error state
Disabled State:
- Use
aria-disabledattribute instead ofdisabledto keep components focusable - Apply
cursor: not-allowedfor disabled elements - Allow disabled elements to receive focus and blur events
- Don't use the
disabledattribute (it removes from focus order)
Focused State:
- Use
:focus-visibleinstead of:focusfor focus indicators - Use
focusStylesutility from@khanacademy/wonder-blocks-styles - Avoid using
box-shadowfor focus indicators
Other States:
- Hover: Style appropriately; consider in combination with other states
- Active/Pressed: Use CSS
:activepseudo-class instead of JavaScript state tracking - Readonly: Prevent editing but allow focus and selection
- Error/Invalid: Provide clear visual indication of validation errors
- Browser Inconsistencies: Test across browsers; use CSS normalization where needed
Use CSS pseudo-classes (:hover, :focus-visible, :active) for state styling instead of JavaScript state tracking. This enables browser dev tools debugging and Storybook Pseudo States add-on for visual regression tests.
- Use semantic HTML or appropriate ARIA roles/attributes
- Ensure keyboard navigation and focus indicators work correctly
- Use
aria-disabledinstead ofdisabledattribute - Prefer visual text for accessible names; use
aria-labeloraria-labelledbywhere necessary - Animations disabled by default, enabled with
animatedprop - Components must work with screen readers and keyboard navigation
- Use logical CSS properties for RTL support
Reference patterns:
We document Wonder Blocks components using Storybook. Documentation includes:
- Document all public props using JSDoc comments (
/** ... */) - The props table on the autodocs page for Storybook should be extracted from the JSDoc comments on the props for a component
- Provide clear descriptions for each prop, especially for complex or non-obvious props
- Include default values and usage examples where helpful
- If the auto-generated type for a prop is not helpful (e.g., something generic like
"union"), the type can be overridden in anargTypes.tsfile - Props in the table can be grouped into categories like
Visual style,Events,Accessibility - Main component should have a JSDoc comment describing its purpose and basic usage
- The stories should showcase the different ways a component can be used
- The comment block before a story declaration can be used to document more about a specific prop or behavior highlighted in the example
- Snapshot stories should include scenarios and state sheets for showing the different variations and states of a component
- Document what's been implemented in the component for accessibility
- Create separate pages in Storybook to describe the accessibility for a component (examples: Accordion Accessibility, Combobox Accessibility, TextArea Accessibility)
packages/wonder-blocks-*/
├── src/
│ ├── index.ts # Package entry point (exports public API)
│ ├── components/
│ │ ├── component-name.tsx
│ │ └── __tests__/
│ │ └── component-name.test.tsx
│ └── util/
└── package.json
- Stories:
__docs__/*.stories.tsx(root__docs__folder) - Export components as named exports (not default exports)
Follow guidance for creating and updating stories:
.agents/skills/storybook/SKILL.md
Follow guidance on unit testing with Jest:
.agents/skills/unit-tests/SKILL.md
| Command | Description |
|---|---|
pnpm start |
Start dev server (Storybook) |
pnpm install |
Install/update dependencies |
pnpm build |
Build all packages |
pnpm lint |
Lint check |
pnpm typecheck |
Type check |
pnpm test |
Run tests |
pnpm build:storybook |
Build Storybook |
pnpm test:storybook |
Run Storybook tests with a11y checks |
pnpm changeset |
Create a changeset |
pnpm changeset --empty |
Empty changeset (tests, stories, tooling changes) |
Don't use pnpx or npx to run commands.
Wonder Blocks runs the Storybook MCP addon (@storybook/addon-mcp) so AI agents can discover components, list stories, and use docs tooling.
- MCP endpoint: When Storybook is running (
pnpm start), the MCP server is athttp://localhost:6061/mcp(this repo uses port 6061, not the default 6006). - Setup: Start Storybook with
pnpm startbefore connecting an MCP client. The addon is configured withtoolsets: { dev: true, docs: true }andexperimentalComponentsManifest: true. - Agent-facing details: See AGENTS.md for connection steps, repo layout for agents, and how to keep documentation agent-friendly.
When working on components or stories, prefer using the MCP tools (list components, get story details) when the client is connected to avoid guessing story IDs or structure.
Wonder Blocks follows Semantic Versioning 2.0.0:
| Version | When to Use | Example |
|---|---|---|
Major (X.0.0) |
Breaking changes | Renaming a prop from enabled to disabled |
Minor (0.X.0) |
New features (backward compatible) | Adding a new error prop |
Patch (0.0.X) |
Bug fixes, internal changes | Fixing border color, upgrading dependencies |
If consumers must change their code for Wonder Blocks to work, it should be a major change.
Don't hesitate to bump major or minor versions when appropriate—following semver correctly is more valuable than trying to minimize version number changes.
CRITICAL: Keep AI assistant rules in sync across platforms.
This project maintains rules for multiple AI assistants. When updating this file, also update the corresponding files for other platforms:
| Claude File | Cursor File | Copilot File | Agent Skills File |
|---|---|---|---|
CLAUDE.md |
.cursor/rules/general.mdc |
.github/instructions/frontend-rules.instructions.md |
(use CLAUDE.md) |
CLAUDE.md (Storybook section) |
(not used — skills file is canonical) | .github/instructions/storybook.instructions.md |
.agents/skills/storybook/SKILL.md |
CLAUDE.md (Jest section) |
(not used — skills file is canonical) | .github/instructions/unit-tests.instructions.md |
.agents/skills/unit-tests/SKILL.md |
When making rule changes:
- Make the change in this
CLAUDE.mdfile - Apply the same change to
.cursor/rules/general.mdcfor general project rules - Apply the same change to the corresponding
.github/instructions/*.instructions.mdfile for Copilot - Apply the same change to
.agents/skills/storybook/SKILL.mdor.agents/skills/unit-tests/SKILL.mdif the change is in those sections
Keep content semantically equivalent:
- The exact formatting may differ between platforms
- The core rules and guidance should remain consistent
- Cursor uses
general.mdcfor project-wide rules; Storybook and Jest rules are handled by agent skills instead - Copilot instructions use standard Markdown in
.github/instructions/ - Agent skills (
.agents/skills/) exist for Wonder Blocks, Storybook and Jest — general conventions live inCLAUDE.md - This
CLAUDE.mdis a consolidated file with all major rules
Never update only one platform's rules - this causes inconsistent behavior between AI assistants.
- TypeScript: Use strict mode, avoid
any, prefer type-only imports - React: Use functional components and hooks, avoid
React.FC - Styling: Use semantic color tokens and
StyleSheet.createfrom Aphrodite - Accessibility: Use
aria-disabledinstead ofdisabled, ensure keyboard navigation works - Components: Small, single-responsibility, with proper TypeScript types
- Props: Follow common patterns (
id,testId,disabled,onChange, etc.) - States: Use CSS pseudo-classes for hover/focus/active styling
- Documentation: JSDoc comments for props, Storybook stories for examples
- Testing: Jest + RTL for behavior, Storybook for visual regression
- Tokens: Use
semanticColor,font, andsizingfrom@khanacademy/wonder-blocks-tokens