Source Cursor rule: .cursor/rules/ui.mdc.
Original Cursor alwaysApply: true.
UI Components
Design System Priority
- First choice:
@trycompai/design-system - Fallback:
@trycompai/uionly if DS doesn't have the component
// ✅ Design system
import { Button, Card, Input, Sheet, Badge } from '@trycompai/design-system';
import { Add, Close, ArrowRight } from '@trycompai/design-system/icons';
// ❌ Don't use when DS has it
import { Button } from '@trycompai/ui/button';
import { Plus } from 'lucide-react';
No className on DS Components
DS components don't accept className. Use variants and props only.
// ✅ Use variants
<Button variant="destructive" size="sm" loading={isLoading}>Delete</Button>
<Button type="submit" iconRight={<ArrowRight size={16} />}>Continue</Button>
<Badge variant="outline">Active</Badge>
// ❌ TypeScript will error
<Button className="bg-red-500">Delete</Button>
Layout with Wrapper Divs
For layout concerns, wrap DS components:
// ✅ Wrapper for width
<div className="w-full">
<Button>Full Width</Button>
</div>
// ✅ Use Stack for spacing
<Stack gap="4" direction="row">
<Button>First</Button>
<Button>Second</Button>
</Stack>
Componentize Repeated Patterns
If a pattern appears 2+ times, extract it:
// Repeated? Make a component
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-green-500" />
<span className="text-sm">Active</span>
</div>
// → Create <StatusDot status="active" />
Extension Strategy
When you need new styling:
- Check existing variants - component may already support it
- Add a variant to the component's
cvadefinition - Create a new component if it's a genuinely new pattern
// Adding a variant
const badgeVariants = cva("...", {
variants: {
variant: {
// existing...
counter: "bg-muted text-muted-foreground tabular-nums font-mono",
},
},
});
Semantic Colors
Use CSS variables, not hardcoded colors:
// ✅ Semantic tokens
<div className="bg-background text-foreground border-border">
<div className="bg-muted text-muted-foreground">
<div className="bg-destructive/10 text-destructive">
// ❌ Hardcoded
<div className="bg-white text-black">
<div className="bg-[#059669]">
Dark Mode
Always support both modes:
// Status colors with dark variants
<div className="bg-green-50 dark:bg-green-950/20 text-green-600 dark:text-green-400">
<div className="bg-red-50 dark:bg-red-950/20 text-red-600 dark:text-red-400">
Icons
Carbon icons from DS, not lucide:
// ✅ Design system icons with size prop
import { Add, Close, ChevronDown } from '@trycompai/design-system/icons';
<Add size={16} />
// ❌ Don't use lucide
import { Plus, X } from 'lucide-react';
<Plus className="h-4 w-4" />
Anti-Patterns
// ❌ Never do these
<div style={{ display: 'flex' }}> // Inline styles
<Button className="bg-red-500"> // className on DS
<div className="bg-[#059669]"> // Hardcoded colors
<div className="w-[847px]"> // Arbitrary values
Responsive (MANDATORY — read the responsive-ui skill)
Every UI change must work on mobile (375px), tablet (768px), desktop (1280px), and
large desktop (1920px) by default — nobody has to ask. Tailwind is mobile-first:
write the base (mobile) layout, widen with sm:/md:/lg:/xl:. No fixed widths
without a responsive strategy (hidden sm:block, w-full md:max-w-xs, or a wrapping
parent). All the rules above still apply: breakpoint classes go on wrapper divs, never
on DS components, and arbitrary pixel values remain an anti-pattern. Wide tables scroll
inside the DS Table (overflow-x-auto), never the page. Full rules + repo patterns +
checklist: .claude/skills/responsive-ui/SKILL.md.