Cursor Rules let you configure how AI Composer generates code. Write rules once, they apply to every AI-generated file. The promise is compelling: encode your team's conventions, architecture preferences, and design system constraints, then let AI follow them automatically.
In practice, most Cursor Rules stay too generic. They say "use our design system" without specifying what that means. They list component names without explaining token structures or composition patterns. AI reads the rules and still generates code that looks plausible but doesn't match your system.
The difference between rules that work and rules that don't is specificity. This guide covers how to write Cursor Rules that actually enforce design system constraints.
What Cursor Rules Can and Can't Do
Cursor Rules are instructions that prepend to every AI prompt. When you write code with Composer, Cursor automatically includes your rules file as context. The AI reads the rules before generating code.
This means Cursor Rules are prompt engineering at scale. Instead of manually typing "use design tokens, follow our naming conventions" in every chat, you write it once in .cursorrules and it applies everywhere.
What this enables:
Enforcing conventions automatically. "Always import design tokens from @/tokens." "Use TypeScript for all new files." "Prefer function components over class components." AI applies these rules without you repeating them.
Teaching custom APIs. If you have a custom component library, Cursor Rules can document the API: "Our <Button> accepts variants: primary, secondary, destructive." AI generates correct code instead of guessing.
Preventing common mistakes. "Never use inline styles." "Don't import from ../../../. Use path aliases." AI avoids pitfalls you've encountered before.
What Cursor Rules cannot do:
Runtime validation. Rules are prompt context, not linters. If AI generates code that violates a rule, there's no automatic error. You catch it in review or testing.
Type enforcement. Rules don't replace TypeScript. If you want to prevent <Button variant="invalid">, that's a TypeScript constraint, not a Cursor Rule.
Multi-file coordination. Rules apply to the current generation context. They don't track state across sessions or ensure consistency between files created weeks apart.
The power is in teaching AI your system's conventions. The limitation is that enforcement is probabilistic, not guaranteed.
Generic Rules vs. Specific Rules
Most Cursor Rules files look like this:
# .cursorrules (generic version)
- Use our design system
- Follow component patterns
- Use semantic naming
- Write clean, maintainable code
- Follow accessibility best practices
These rules feel correct but are too vague to change AI behavior. Every instruction is an unmeasurable platitude. "Use clean code" means nothing to AI—it doesn't have a definition of "clean."
Contrast with specific rules:
# .cursorrules (specific version)
## Design Tokens
- Import tokens from @/design-tokens
- Use token references, never hardcoded values
- Valid color tokens: color.action.{primary|secondary|destructive}, color.surface.{background|card|elevated}, color.text.{primary|secondary|tertiary}
- Spacing tokens follow 4px scale: spacing.{1|2|3|4|6|8|12|16|24|32}
- Example: `backgroundColor: tokens.color.action.primary` not `backgroundColor: "#3b82f6"`
## Component Library
- Import components from @/components/ui
- Button variants: default, destructive, outline, secondary, ghost, link
- Button sizes: default, sm, lg, icon
- Card composition: <Card><CardHeader><CardTitle/><CardDescription/></CardHeader><CardContent/><CardFooter/></Card>
- Never invent new component variants. If needed, ask for guidance.
## File Structure
- Place new components in src/components/
- Co-locate tests: ComponentName.tsx + ComponentName.test.tsx
- Use path alias @/ for src/, never relative imports like ../../../
## TypeScript
- Use `interface` for component props, `type` for unions
- Export types alongside components
- No `any` types. Use `unknown` if type is truly dynamic.
## Forbidden Patterns
- No inline styles. Use className with Tailwind or style prop with token references.
- No `!important` in CSS
- No `console.log` in production code. Use proper logging library.
The second version is actionable. AI can read "valid color tokens: color.action.{primary|secondary|destructive}" and know exactly what's allowed. It can read "Button variants: default, destructive, outline" and avoid hallucinating variant="super-primary".
The rule: if AI can't generate code directly from the instruction, the instruction is too vague.
Documenting Token Structures in Rules
Design tokens are the foundation of AI-generated design system consistency. Your Cursor Rules should make token structure completely explicit.
Bad token documentation:
- Use design tokens
AI doesn't know what tokens exist or how to reference them.
Good token documentation:
## Design Tokens (import from @/design-tokens)
### Color Tokens
Semantic color roles organized by intent:
- Action colors: color.action.{primary|secondary|destructive}.{default|hover|active|disabled}
- Surface colors: color.surface.{background|card|elevated|overlay}
- Text colors: color.text.{primary|secondary|tertiary|inverse|disabled}
- Border colors: color.border.{default|hover|focus}
Usage: `style={{ backgroundColor: tokens.color.action.primary.default }}`
CSS variables: `var(--color-action-primary-default)`
### Spacing Tokens
Follow 4px base unit:
- spacing.1 = 4px
- spacing.2 = 8px
- spacing.3 = 12px
- spacing.4 = 16px
- spacing.6 = 24px
- spacing.8 = 32px
- spacing.12 = 48px
- spacing.16 = 64px
Usage: `style={{ padding: tokens.spacing.4 }}`
For Tailwind: Use spacing scale (p-4, m-6, gap-8)
### Typography Tokens
Predefined text styles:
- typography.hero: 48px/1.1, bold
- typography.h1: 36px/1.2, bold
- typography.h2: 30px/1.3, semibold
- typography.body: 16px/1.5, regular
- typography.label: 14px/1.4, medium
- typography.caption: 12px/1.4, regular
Usage: `className="typography-body"` or spread typography.body object into style prop
Now AI knows:
- Tokens are organized by semantic role (action, surface, text)
- Each token has modifiers (hover, active, disabled)
- How to reference tokens in code
- Valid token names (can't invent color.action.super-primary)
This level of detail transforms "use design tokens" from vague guidance to actionable constraint.
Encoding Component APIs
Document every component API that AI might generate. This prevents hallucinated props and invalid compositions.
Component with clear API rules:
## Button Component (import from @/components/ui/Button)
Props:
- variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
- size?: "default" | "sm" | "lg" | "icon"
- disabled?: boolean
- children: React.ReactNode
Examples:
Primary CTA: <Button variant="default">Submit</Button>
Destructive action: <Button variant="destructive">Delete</Button>
Secondary action: <Button variant="outline">Cancel</Button>
Small button: <Button size="sm">Close</Button>
Do NOT:
- Invent variants like "super-primary" or "cta"
- Use size="xs" or size="xl" (not defined)
- Pass color prop (use variant instead)
Card component with composition rules:
## Card Component (import from @/components/ui/Card)
Composition:
<Card>
<CardHeader>
<CardTitle>Title text</CardTitle>
<CardDescription>Optional description</CardDescription>
</CardHeader>
<CardContent>
Main content goes here
</CardContent>
<CardFooter>
Optional footer with actions
</CardFooter>
</Card>
Rules:
- CardHeader is optional but recommended for titled cards
- CardContent is required
- CardFooter is optional
- Order matters: Header → Content → Footer
- Do not nest Cards without semantic purpose
Example for settings panel:
<Card>
<CardHeader>
<CardTitle>Account Settings</CardTitle>
</CardHeader>
<CardContent>
<form>...</form>
</CardContent>
<CardFooter>
<Button variant="default">Save</Button>
<Button variant="outline">Cancel</Button>
</CardFooter>
</Card>
When AI generates a card, it follows the documented pattern. No missing CardContent wrappers. No invented CardBody component. Composition rules prevent structural mistakes.
Architecture and File Structure Rules
AI should know where files go and how to organize code. This prevents inconsistent project structure.
## Project Structure
### Component Placement
- UI primitives: src/components/ui/ (Button, Input, Card, etc.)
- Feature components: src/components/features/ (LoginForm, ProductCard, CheckoutWizard)
- Layout components: src/components/layout/ (Header, Sidebar, Footer)
- Page components: src/app/ or src/pages/ (depending on router)
### File Naming
- Components: PascalCase (Button.tsx, CardHeader.tsx)
- Utilities: camelCase (formatDate.ts, apiClient.ts)
- Hooks: camelCase with "use" prefix (useAuth.ts, useLocalStorage.ts)
- Types: PascalCase (UserProfile.ts, ApiResponse.ts)
### Co-location
- Tests next to source: Button.tsx + Button.test.tsx
- Styles with components if needed: Button.tsx + Button.module.css
- Types can be in same file for small components, separate for shared types
### Import Aliases
- @/ points to src/
- @/components/ for components
- @/lib/ for utilities
- @/design-tokens for tokens
Never use relative imports like ../../../../components/Button
### File Organization Example
src/
components/
ui/
Button.tsx
Button.test.tsx
Card.tsx
Card.test.tsx
features/
LoginForm.tsx
LoginForm.test.tsx
design-tokens/
index.ts
colors.ts
spacing.ts
lib/
utils.ts
api.ts
Now when AI creates a new component, it knows:
- Where to place the file
- How to name it
- What import alias to use
- Whether to co-locate tests
Project structure stays consistent across AI-generated files.
TypeScript and Type Safety Rules
AI's default TypeScript usage is permissive. Rules can enforce stricter patterns.
## TypeScript Standards
### Component Props
- Use `interface` for props, `type` for unions
- Export prop types alongside component
- Mark optional props with `?`, don't use `undefined` unions
Good:
interface ButtonProps {
variant?: "default" | "destructive"
disabled?: boolean
children: React.ReactNode
}
Bad:
type ButtonProps = {
variant: "default" | "destructive" | undefined // Use ? instead
disabled: boolean // Should be optional
}
### Event Handlers
- Use React event types, not generic Function
- Name handlers with "handle" prefix
Good:
interface FormProps {
onSubmit: (data: FormData) => void
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
}
function handleSubmit(data: FormData) { ... }
Bad:
interface FormProps {
onSubmit: Function // Too generic
}
const submit = (data: any) => { ... } // any is forbidden
### Forbidden
- No `any` types. Use `unknown` if type is truly unknown, then narrow with type guards
- No `@ts-ignore` or `@ts-expect-error` without explanation comment
- No unused variables or imports
- No implicit `any` (check tsconfig.json has `noImplicitAny: true`)
### Generics
When creating reusable components, use constrained generics:
Good:
interface SelectProps<T extends string | number> {
value: T
options: Array<{ value: T; label: string }>
}
Bad:
interface SelectProps {
value: any
options: any[]
}
These rules push AI toward type-safe patterns instead of permissive shortcuts.
Accessibility and Best Practices
Encode non-functional requirements that AI often skips:
## Accessibility Requirements
### Semantic HTML
- Use semantic elements: <button>, <nav>, <main>, <article>, <aside>
- Don't use <div> with onClick. Use <button>.
- Use <label> with form inputs
### ARIA Labels
- Add aria-label to icon-only buttons: <Button aria-label="Close dialog"><X /></Button>
- Use aria-describedby for error messages
- Add role attributes when semantic HTML isn't sufficient
### Keyboard Navigation
- All interactive elements must be keyboard accessible
- Modals must trap focus
- Add visible focus indicators (don't remove outline without replacement)
### Color Contrast
- Text must meet WCAG AA standards (4.5:1 for body, 3:1 for large text)
- Don't rely on color alone to convey information
- Use color.text.primary on color.surface.background (verified contrast ratios)
### Form Validation
- Provide error messages with specific guidance
- Mark required fields clearly
- Don't rely on placeholder text for labels
Example of accessible form:
<form>
<label htmlFor="email">Email address *</label>
<Input
id="email"
type="email"
required
aria-describedby="email-error"
/>
{error && (
<p id="email-error" role="alert">
{error}
</p>
)}
</form>
AI won't remember accessibility rules without explicit encoding. These instructions make accessible patterns the default.
Testing and Quality Rules
Specify testing expectations:
## Testing Standards
### Test Co-location
- Every component gets a test file: Button.tsx → Button.test.tsx
- Place test next to source, not in separate __tests__ folder
### What to Test
- Render without errors
- Props work correctly (variants, sizes, disabled state)
- User interactions (click handlers, form submission)
- Accessibility (keyboard navigation, ARIA labels)
### Test Structure
Use Vitest and Testing Library:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Button } from './Button'
describe('Button', () => {
it('renders with default variant', () => {
render(<Button>Click me</Button>)
expect(screen.getByRole('button')).toBeInTheDocument()
})
it('calls onClick handler', async () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click me</Button>)
await userEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledOnce()
})
})
### Don't Test
- Implementation details (internal state, class names)
- Third-party library behavior
With these rules, AI generates tests alongside components automatically.
Example: Complete .cursorrules File
A production-ready Cursor Rules file combining all patterns:
# .cursorrules for [Your Project]
## Design System
### Design Tokens (import from @/design-tokens)
- Always use tokens, never hardcoded values
- Color tokens: color.action.{primary|secondary|destructive}.{default|hover|active|disabled}
- Spacing tokens: spacing.{1|2|3|4|6|8|12|16} (4px base unit)
- Typography: typography.{hero|h1|h2|body|label|caption}
- Import: `import { tokens } from '@/design-tokens'`
- Usage: `style={{ backgroundColor: tokens.color.action.primary.default }}`
### Component Library (@/components/ui)
Button:
- Variants: default, destructive, outline, secondary, ghost, link
- Sizes: default, sm, lg, icon
- Example: `<Button variant="default" size="sm">Submit</Button>`
Card:
- Composition: `<Card><CardHeader><CardTitle/></CardHeader><CardContent/></Card>`
- CardHeader optional, CardContent required, CardFooter optional
Input:
- Always pair with <Label>
- Use aria-describedby for errors
- Example: `<Label htmlFor="email">Email</Label><Input id="email" type="email" />`
## Architecture
### File Structure
- UI components: src/components/ui/
- Feature components: src/components/features/
- Use @/ alias, never ../../../
- Co-locate tests: Component.tsx + Component.test.tsx
### TypeScript
- Use `interface` for props, `type` for unions
- Export prop types
- No `any` — use `unknown` then type-narrow
- React event types: `React.ChangeEvent`, `React.MouseEvent`
## Standards
### Accessibility
- Semantic HTML: <button> not <div onClick>
- aria-label for icon-only buttons
- <label> for all inputs
- Focus indicators visible
### Testing
- Generate tests with components
- Test user interactions, not implementation
- Use Testing Library queries: getByRole, getByLabelText
### Forbidden
- Inline styles (use className or tokens)
- Hardcoded colors/spacing
- `console.log` (use logger)
- Inventing component variants not listed above
## Examples
Good:
<Button variant="destructive" size="sm">Delete</Button>
Bad:
<Button variant="super-primary" color="red">Delete</Button>
Good:
<Card>
<CardHeader><CardTitle>Settings</CardTitle></CardHeader>
<CardContent>...</CardContent>
</Card>
Bad:
<Card>
<CardBody>...</CardBody> // CardBody doesn't exist
</Card>
Save this as .cursorrules in your project root. Cursor loads it automatically.
Measuring Rules Effectiveness
How do you know if your Cursor Rules work? Track AI-generated code quality:
Token compliance rate:
# Count CSS variable usage vs hardcoded colors
grep -r "var(--color-" src/ | wc -l # Good
grep -rE "#[0-9a-f]{6}" src/ | wc -l # Bad
Aim for 95%+ of colors coming from tokens.
Component API correctness:
Review AI-generated components for hallucinated props. If AI generates <Button variant="super-primary">, your rules aren't specific enough.
Architecture consistency:
Check if new AI-generated files land in correct directories. If AI creates components/ButtonThing.tsx instead of components/ui/Button.tsx, file structure rules need clarification.
Test coverage: Do AI-generated components include tests? If not, testing rules aren't clear or prominent enough.
The goal is 90%+ of AI-generated code following conventions without manual correction.
Integrating with FramingUI
FramingUI projects can use pre-built Cursor Rules templates:
npx framingui init --cursor-rules
This generates .cursorrules with:
- Complete token structure documentation
- All FramingUI component APIs
- Recommended architecture patterns
- TypeScript standards
- Accessibility requirements
You customize from there. The template provides the foundation; you add project-specific rules (API patterns, domain models, testing preferences).
With FramingUI's MCP server + Cursor Rules, AI has two layers of context:
- MCP server — runtime queryable design system contract
- Cursor Rules — prompt-level conventions and examples
The combination ensures AI-generated code matches your design system consistently.
Cursor Rules are prompt engineering for your entire codebase. Generic rules produce generic results. Specific rules—explicit token structures, documented component APIs, clear architecture patterns—produce code that matches your design system without manual cleanup. Write rules that AI can act on directly, and AI-generated code becomes indistinguishable from hand-written code that follows your conventions.