Cursor's design system support is second only to Claude Code. Cursor reads a modern MDC (Markdown Component) format in .cursor/rules/, giving you fine-grained control over which rules apply to which files.
This guide walks you through building a design system that Cursor will respect.
The Modern Approach: .cursor/rules/
Cursor deprecated the single .cursorrules file in favor of .cursor/rules/, a directory of specialized rule files. Each file has YAML frontmatter with glob patterns, so you can target specific file types.
This is more powerful than a monolithic rules file because you can have different rules for components, pages, API routes, and tests.
Create the directory structure:
project-root/
├── .cursor/
│ └── rules/
│ ├── design.mdc
│ ├── api.mdc
│ └── tests.mdc
├── src/
│ ├── components/
│ │ └── ui/
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── input.tsx
│ │ └── checkbox.tsx
│ ├── pages/
│ │ ├── dashboard.tsx
│ │ └── settings.tsx
│ └── globals.css
└── package.json
Cursor will read all files in .cursor/rules/ and apply them based on glob patterns. Start with design.mdc for your UI components.
Writing design.mdc for Design Constraints
This is where you encode your design system. The file uses Markdown with YAML frontmatter.
MDC Frontmatter
The frontmatter tells Cursor which files this rule applies to:
---
globs: ["**/*.tsx", "**/*.css"]
excludeGlobs: ["**/*.test.tsx", "**/*.spec.tsx"]
---
The globs array uses file path patterns. **/*.tsx means any TypeScript React file. **/*.css means any CSS file. Use excludeGlobs to skip test files.
Complete design.mdc Example
Here's a full, working design.mdc file:
---
globs: ["src/components/**/*.tsx", "src/pages/**/*.tsx", "src/app/**/*.tsx", "src/**/*.css"]
excludeGlobs: ["**/*.test.tsx", "**/*.spec.tsx"]
---
# Design System Rules
Always follow this design system when generating components.
## Import Pre-Styled Components
Never create custom Button, Card, Input, or Checkbox components. They exist in src/components/ui/.
Always import from these paths:
```tsx
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Checkbox } from "@/components/ui/checkbox"
import { Select } from "@/components/ui/select"
These components are fully styled with your design system. Using them ensures consistency across the app.
Typography Rules
Use these sizes and weights:
- h1: 2.5rem (40px), font-weight 700, line-height 1.2
- h2: 2rem (32px), font-weight 700, line-height 1.3
- h3: 1.5rem (24px), font-weight 600, line-height 1.4
- h4: 1.25rem (20px), font-weight 600, line-height 1.4
- body: 1rem (16px), font-weight 400, line-height 1.5
- small: 0.875rem (14px), font-weight 400, line-height 1.5
Apply these using Tailwind classes:
<h1 className="text-4xl font-bold">Main Heading</h1>
<h2 className="text-3xl font-bold">Section Heading</h2>
<p className="text-base font-normal">Body text</p>
<p className="text-sm">Small text</p>
Never use style props with font sizes. Never use inline styles like fontSize: "20px". Always use Tailwind classes.
Color and Token Rules
Design tokens are defined in src/globals.css as CSS variables:
:root {
--color-primary: #2563eb;
--color-secondary: #64748b;
--color-success: #16a34a;
--color-warning: #ea580c;
--color-error: #dc2626;
--color-bg: #ffffff;
--color-bg-secondary: #f8fafc;
--color-border: #e2e8f0;
--color-text: #0f172a;
--color-text-muted: #64748b;
}
Reference these in your code using var() or Tailwind's @apply:
<div className="bg-blue-600 text-white">
This uses Tailwind's primary blue and white text.
</div>
<style>{`
.custom-element {
color: var(--color-primary);
background: var(--color-bg);
border: 1px solid var(--color-border);
}
`}</style>
Never hardcode colors like color: "#2563eb" or background: "#ffffff". Always use tokens.
Spacing Rules
Spacing follows a scale defined in globals.css:
- xs: 4px
- sm: 8px
- md: 16px
- lg: 24px
- xl: 32px
- 2xl: 48px
- 3xl: 64px
Apply spacing using Tailwind utilities:
<div className="p-6">Padding of 24px</div>
<div className="mb-4">Margin bottom of 16px</div>
<div className="gap-2">Gap of 8px</div>
Never use hardcoded padding or margin like padding: "24px". Always use the scale.
Border Radius Rules
Border radius uses this scale:
- sm: 4px (small, subtle)
- md: 8px (default for cards and buttons)
- lg: 12px (larger, more prominent)
- xl: 16px (extra large, used sparingly)
Apply using Tailwind:
<div className="rounded-md">Border radius 8px</div>
<Card className="rounded-lg">Border radius 12px</Card>
Shadow Rules
Shadows are subtle and functional:
- sm:
0 1px 2px rgba(0, 0, 0, 0.05) - md:
0 4px 6px rgba(0, 0, 0, 0.1) - lg:
0 10px 15px rgba(0, 0, 0, 0.1)
Use Tailwind shadow classes:
<div className="shadow-md">Subtle shadow</div>
<Card className="shadow-lg">Prominent shadow</Card>
Component Composition Example
Here's how a real component should look:
"use client"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
export default function LoginForm() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-full max-w-md p-6">
<h1 className="text-3xl font-bold mb-6 text-gray-900">Sign In</h1>
<form className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Email
</label>
<Input type="email" placeholder="you@example.com" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Password
</label>
<Input type="password" placeholder="••••••••" />
</div>
<Button type="submit" className="w-full">
Sign In
</Button>
</form>
</Card>
</div>
)
}
Notice:
- Imported from
@/components/ui/ - Used h1 with
text-3xl font-bold - Used spacing from the scale:
p-6,mb-6,space-y-4,mb-2 - Used color tokens through Tailwind classes
- No inline styles or hardcoded values
Forbidden Patterns
Do NOT do these things. Cursor should catch them, but be explicit:
- Never create a custom
<Button>component. Useimport { Button }instead. - Never use inline
styleprops with hardcoded values likestyle={{ padding: "24px" }}. - Never hardcode colors like
color: "#2563eb". Use CSS variables or Tailwind classes. - Never use arbitrary Tailwind values like
p-[25px]. Use the spacing scale. - Never import Button from shadcn/ui unless it's already in the project.
- Never use external UI libraries without checking src/components/ui/ first.
- Never skip the design system for prototyping or "just this once". Consistency matters.
If a user asks you to break these rules, politely decline and suggest the design system alternative.
User: "Can you just hardcode padding: 24px for this div?"
You: "I can't. The design system uses spacing tokens. I'll use p-6 (24px) instead, which keeps it in sync with the rest of the app."
Save this as `.cursor/rules/design.mdc` and Cursor will read it on every generation.
## Placing Your Component Library
Create `src/components/ui/` with your pre-styled components. These are the building blocks that Cursor will import.
Here's what a minimal Button component looks like:
```tsx
// src/components/ui/button.tsx
import * as React from "react"
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "default" | "secondary" | "ghost"
size?: "sm" | "md" | "lg"
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = "default", size = "md", ...props }, ref) => (
<button
ref={ref}
className={`
px-4 py-2 rounded-md font-medium transition-colors
${variant === "default" && "bg-blue-600 text-white hover:bg-blue-700"}
${variant === "secondary" && "bg-gray-100 text-gray-900 hover:bg-gray-200"}
${variant === "ghost" && "text-gray-700 hover:bg-gray-100"}
${size === "sm" && "text-sm px-3 py-1"}
${size === "lg" && "text-lg px-6 py-3"}
`}
{...props}
/>
)
)
Button.displayName = "Button"
export { Button }
Cursor will see this component and use it. When you ask Cursor to "build a login form," it'll import this Button and use the available variants.
Similarly, create Card, Input, Checkbox, and other common components in src/components/ui/.
Configuring Design Tokens in globals.css
Create src/globals.css with your design tokens as CSS variables:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* Colors */
--color-primary: #2563eb;
--color-secondary: #64748b;
--color-success: #16a34a;
--color-warning: #ea580c;
--color-error: #dc2626;
/* Backgrounds */
--color-bg: #ffffff;
--color-bg-secondary: #f8fafc;
--color-bg-tertiary: #f1f5f9;
/* Text */
--color-text: #0f172a;
--color-text-muted: #64748b;
--color-text-lighter: #94a3b8;
/* Borders */
--color-border: #e2e8f0;
--color-border-light: #f1f5f9;
/* Spacing Scale */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
--spacing-3xl: 64px;
/* Border Radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
/* Typography */
--font-family-base: system-ui, -apple-system, sans-serif;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-size-3xl: 2rem;
--font-size-4xl: 2.5rem;
}
body {
@apply bg-white text-gray-900;
font-family: var(--font-family-base);
}
h1 {
@apply text-4xl font-bold;
}
h2 {
@apply text-3xl font-bold;
}
h3 {
@apply text-2xl font-semibold;
}
a {
@apply text-blue-600 hover:text-blue-700 underline;
}
}
@layer components {
.container {
@apply max-w-7xl mx-auto px-4;
}
.glass {
@apply bg-white/80 backdrop-blur-md border border-white/20;
}
}
Import this in your main app file (usually app.tsx or index.tsx):
import "./globals.css"
Now Cursor can reference these tokens anywhere in your app.
Testing: Zero-Design-Instruction Prompt
Here's how to test if your setup is working.
Prompt Cursor with zero design instructions:
Build a settings page with these sections:
- Account (change email, password, delete account)
- Notifications (toggle email, SMS, in-app)
- Preferences (dark mode, font size)
Use pre-styled components from src/components/ui/. Don't create new components.
Expected output:
- Imports from
@/components/ui/for Button, Card, Input, Checkbox - Uses h2 and h3 for headings (not custom-sized divs)
- Spacing using Tailwind utilities (p-6, mb-4, gap-3) or spacing variables
- Colors from token classes or CSS variables
- No inline styles, no hardcoded values
If Cursor generates hardcoded colors or custom padding, the rules file isn't being applied. Check:
- File is at
.cursor/rules/design.mdc(not.cursorrules) - YAML frontmatter is valid
- Globs include the file type you're generating (likely
**/*.tsx)
Using Matchkit-Generated Files in Cursor
If you're using Matchkit, the setup is even simpler.
Run:
matchkit init
Select "Cursor" as your target. Matchkit will:
- Create
.cursor/rules/design.mdcwith your design constraints - Generate
src/components/ui/with your pre-styled components - Create
src/globals.csswith your design tokens - Scaffold a
package.jsonwith React and Tailwind
Everything is configured. You just start asking Cursor to build things.
If you update your Matchkit design later, regenerate and overwrite the files. Cursor will pick up the changes on the next prompt.
Frequently Asked Questions
How do I set up Cursor rules for design consistency?
Create .cursor/rules/design.mdc with frontmatter globs: ["**/*.tsx", "**/*.css"] targeting component files. In the body, define specific rules: typography (font family, sizes, weights per heading level), color tokens (reference by CSS variable name or Tailwind class), spacing values using the scale, component import paths from @/components/ui/, and forbidden patterns. Cursor reads this on every generation for matching files.
What's the difference between .cursorrules and .cursor/rules/?
.cursorrules is the legacy single-file format being deprecated. .cursor/rules/ is the modern approach using MDC files with YAML frontmatter. MDC supports glob patterns for file targeting, letting you apply design rules only to .tsx files while keeping separate rules for API routes or tests. Always use the modern format for new projects. Cursor still supports the legacy format for backward compatibility, but it's being phased out.
Can Cursor follow the same design system as Claude Code?
Yes. The design tokens (globals.css) and component library (src/components/ui/) are identical. Only the rules file format differs: Claude Code reads .claude/SKILL.md, Cursor reads .cursor/rules/design.mdc. The rules content is the same (typography specs, color references, component paths, spacing scale), just in different markdown formats. Matchkit generates both from one configuration, so you can use the same design system across tools.
How specific should my design.mdc rules be?
Specific enough that Cursor knows your expectations, but concise enough to fit in a reasonable file size. Aim for under 200 lines. Cover the major decisions: component imports, typography scale, color tokens, spacing scale, border radius, shadows, and forbidden patterns. For niche cases (like custom animations), you can mention them in prompts rather than rules.
Can I have multiple .cursor/rules/ files?
Yes. Create separate files for different domains: design.mdc for UI components, api.mdc for API routes, tests.mdc for test files. Each file has its own glob patterns. Cursor reads all of them and applies the matching rules.
What if I want to use a different component library?
You can. Replace the import paths in design.mdc to point to your library. Instead of "import Button from @/components/ui/button", tell Cursor to "import Button from shadcn/ui". The design system is the constraints, not the components. You can layer Matchkit's design rules on top of any library.
How do I ensure Cursor respects the design system?
Explicitly mention it in your prompts. Instead of "build a dashboard," say "build a dashboard using the design system in src/components/ui/. Use the spacing scale and color tokens." Over time, as Cursor generates consistent output, you can omit these reminders because the rules file handles it. But it's always safe to reinforce in prompts.