Comprehensive rules for building a modern, accessible, high-performance React/TypeScript component library.
Your team keeps rebuilding the same components. Button variants. Form inputs. Modal dialogs. Loading states. Each project starts with the same tedious component scaffolding, and accessibility always gets pushed to "later" (which never comes).
Building components isn't hard. Building maintainable, accessible, performant components that scale across teams is. Most component libraries either:
You need components that work like Lego blocks: composable, reliable, and built for the long haul.
These Cursor Rules establish a bulletproof component library foundation that solves the real problems: accessibility-first design, zero-runtime styling, composable APIs, and enterprise-grade testing.
Here's what you get:
// Accessible by default, fully typed, composable
<Dialog>
<DialogTrigger asChild>
<Button variant="primary" size="md">Open Settings</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>User Preferences</DialogTitle>
</DialogHeader>
<Form onSubmit={handleSubmit}>
<FormField name="email" label="Email" required />
<FormActions>
<Button type="submit">Save Changes</Button>
</FormActions>
</Form>
</DialogContent>
</Dialog>
Every component ships with ARIA attributes, keyboard navigation, focus management, and screen reader support—no extra configuration needed.
// Before: Manual ARIA management
const [isOpen, setIsOpen] = useState(false);
const [focusedIndex, setFocusedIndex] = useState(-1);
// 50+ lines of keyboard handlers, focus traps, aria-describedby...
// After: Built-in accessibility
<Dropdown items={menuItems} onSelect={handleSelect} />
Time saved: 2-3 hours per interactive component
// CSS-in-JS that generates at build time, not runtime
export const buttonStyles = style({
base: {
display: 'inline-flex',
alignItems: 'center',
fontSize: tokens.fontSize.sm,
},
variants: {
size: {
sm: { padding: '8px 12px' },
md: { padding: '12px 16px' },
lg: { padding: '16px 24px' }
}
}
});
Performance impact: <5kB per component, zero runtime style injection
interface ButtonProps {
variant: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
isDisabled?: boolean;
children: React.ReactNode;
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
}
// Exhaustive type checking catches missing cases at compile time
const getButtonVariant = (variant: ButtonProps['variant']) => {
switch (variant) {
case 'primary': return primaryStyles;
case 'secondary': return secondaryStyles;
case 'ghost': return ghostStyles;
default:
const _exhaustive: never = variant; // TypeScript error if case missing
throw new Error(`Unhandled variant: ${variant}`);
}
};
Scenario: Your PM wants a new notification system by Friday.
# Generate component scaffolding
cursor-generate notification-toast
# Auto-generated structure:
src/components/toast/
├── Toast.tsx # Component logic
├── Toast.css.ts # Zero-runtime styles
├── Toast.stories.tsx # Storybook documentation
├── Toast.test.tsx # Accessibility tests
└── index.ts # Clean exports
Implementation time: 15 minutes instead of 2+ hours
// Update design tokens once
export const tokens = {
colors: {
primary: {
50: '#eff6ff',
500: '#3b82f6',
900: '#1e3a8a'
}
}
};
// All components automatically inherit changes
// No manual CSS updates across 12 different button implementations
// Every component includes accessibility tests
it('supports keyboard navigation', async () => {
render(<Dropdown items={mockItems} />);
await userEvent.tab(); // Focus trigger
await userEvent.keyboard('{ArrowDown}'); // Open menu
await userEvent.keyboard('{ArrowDown}'); // Navigate items
await userEvent.keyboard('{Enter}'); // Select item
expect(mockOnSelect).toHaveBeenCalledWith(expectedItem);
});
// axe-core runs in CI - failing accessibility fails the build
npm create vite@latest my-component-lib -- --template react-ts
cd my-component-lib
# Install the full stack
npm install @radix-ui/react-* tailwindcss @vanilla-extract/css
npm install -D storybook jest @testing-library/react playwright
Copy the complete Cursor Rules configuration into your .cursorrules file. This gives you:
// src/components/button/Button.tsx
import { forwardRef } from 'react';
import { clsx } from 'clsx';
import { buttonStyles } from './Button.css';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', size = 'md', isLoading = false, className, children, ...props }, ref) => {
return (
<button
ref={ref}
className={clsx(buttonStyles({ variant, size }), className)}
aria-busy={isLoading}
disabled={isLoading || props.disabled}
{...props}
>
{isLoading ? <Spinner aria-hidden /> : null}
{children}
</button>
);
}
);
// Button.stories.tsx - Generated automatically
export default {
component: Button,
parameters: {
a11y: { disable: false }, // Accessibility panel enabled
}
};
export const Interactive = () => (
<div className="space-y-4">
<Button variant="primary">Primary Button</Button>
<Button variant="secondary" size="lg">Large Secondary</Button>
<Button isLoading>Loading State</Button>
</div>
);
// IntelliSense shows exact props, variants, and accessibility requirements
<Button
variant="primary" // TypeScript autocomplete
size="md" // All options suggested
onClick={handler} // Properly typed event handlers
aria-label="Save document" // Accessibility hints included
>
Save Changes
</Button>
Your components become the foundation other teams want to use, not the liability they avoid. Stop rebuilding. Start scaling.
The complete Cursor Rules configuration handles the complexity—you focus on shipping features that matter.
You are an expert in React, TypeScript, Vite, Storybook, Jest, Playwright, Radix UI Primitives, Tailwind CSS (JIT), vanilla-extract, and a11y tooling (React Aria, axe-core).
Key Principles
- Ship accessibility-first components: ARIA roles/attributes, keyboard navigation, focus traps, screen-reader text.
- Favour composability over monolithic APIs ("headless" patterns with minimal styling contracts).
- 100 % TypeScript with strictNullChecks & noImplicitAny; public API must be fully typed.
- Zero-runtime styling: prefer vanilla-extract or Tailwind classes generated at build-time.
- Performance budget: <5 kB per component (gzipped) and <50 ms mount time; apply tree-shaking & lazy loading.
- Theming via CSS variables with fallback tokens; dark-mode support out-of-the-box.
- Immutable data, pure render functions, and memoization to avoid unnecessary re-renders.
- Document every component in Storybook with live props, controls, a11y panel, and responsive viewport presets.
- Continuous testing pipeline: unit + integration (Jest/RTL), visual regression (Chromatic), e2e (Playwright).
TypeScript
- Enable `strict`, `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess` in tsconfig.
- Export both ESM (module) and CJS (commonjs) build targets plus d.ts files.
- Use `interface` for public props, `type` for unions & utility generics.
- Props naming:
• Boolean flags start with `is` / `has` (isOpen, hasShadow)
• Event callbacks start with `on` and use `React.*Event` generics (onSelect)
• Enumerations are discriminated string unions (`size?: "sm" | "md" | "lg"`).
- Avoid default exports; use named exports to optimise tree-shaking.
Error Handling and Validation
- Guard clauses at top of functions; return early on invalid input.
- Throw domain-specific errors with machine-readable `code` property (e.g., `new ComponentError('MISSING_ID', msg)`).
- Validate required ARIA attributes in development build; log warning via `process.env.NODE_ENV!=='production'`.
- Leverage TypeScript’s exhaustive type checking with `never` to catch unhandled cases.
- Provide a global `ErrorBoundary` example in docs; encourage consumers to wrap root.
React
- Functional components only; no class components.
- Accept `ref` via `forwardRef` and expose correct type (`React.Ref<HTMLButtonElement>`).
- Side-effects inside `useEffect`; avoid layout thrashing.
- Use `useCallback`/`useMemo` for expensive operations; do not memoise blindly.
- Accessibility helpers: `useId()` for id relationship, `mergeRefs()` for multiple refs.
- Prefer Radix UI primitives for focus management, dismissable layers, scroll locking.
Styling (Tailwind CSS & vanilla-extract)
- For Tailwind: compose utility classes via `clsx`; never concatenate raw strings.
- Use `@apply` only in designated `*.css` files to avoid style leaks.
- Provide design tokens as CSS variables (`--color-primary-500`) generated from Figma.
- Support RTL by flipping spacing and logical properties, not hard-coded `left/right`.
Build & Packaging
- Tooling: Vite + `rollup-plugin-dts`.
- Entry point barrel file re-exports every component; keep private helpers under `internal/`.
- Generate both `.mjs` and `.cjs`; mark sideEffects:false in package.json for better tree-shaking.
- Auto-generate changelog via Conventional Commits and `semantic-release`.
Testing
- Unit: Jest + React Testing Library; write tests in `__tests__/Component.test.tsx`.
- Coverage ≥ 90 %. Ensure aria attributes and keyboard flows in tests (`userEvent.tab()`).
- Snapshot tests only for stable markup (tokens, layout) – avoid broad snapshots.
- Playwright E2E stories run against Storybook static build; include mobile viewport.
- Accessibility: run `axe` in CI; fail build if violations > severity 3.
Performance
- Export a `lazy()` wrapped version of heavy components (e.g., charts).
- Use dynamic import comments for webpack chunk names (`import(/* webpackChunkName: "modal" */ './Modal')`).
- Debounce expensive handlers (`resize`, `scroll`) and detach listeners on unmount.
- Memoise context value objects to prevent cascaded renders.
Security
- Sanitize any HTML string props with DOMPurify before `dangerouslySetInnerHTML`.
- Opt-in CSP safe inline styles (`style-src 'self' 'unsafe-hashes'`).
- Validate URLs via `new URL()` to prevent XSS injection.
Documentation
- Storybook MDX for docs + code snippets; include `Playground` with live editing.
- Auto-generate prop tables from TypeScript (`storybook-addon-react-docgen`) ; flag deprecated props.
- Provide migration guides for breaking changes.
Directory Structure
- src/
• components/
◦ button/
▪︎ Button.tsx
▪︎ Button.css.ts
▪︎ index.ts
◦ dropdown/
• hooks/
• utils/
• themes/
• internal/
- test/
- .storybook/
Commit & CI
- Use Husky + lint-staged (eslint --max-warnings 0, prettier).
- Enforce Conventional Commits; run `commitlint`.
- On PR: type-check, build, test, a11y, bundle-size check (<3 % regress).
Common Pitfalls & How to Avoid
- Missing focus states → always pair interactive component with `:focus-visible` outline.
- Non-deterministic ids → rely on `useId` instead of `Math.random()`.
- Event propagation causing double handlers → stop propagation only when necessary; document behaviour.
- Over-styling base component → expose `className`/`style` passthrough; keep default styles minimal.