components · git:20260103.9f62587 · 2026-01-03 · sha256 e8f969e7cb94daa7

components git:20260103.9f62587A

Immutable. This exact content is served forever at /api/v1/blob/e8f969e7cb94daa7.

---
description: Component development patterns - Shadcn first, atomic design, Figma parity
globs: src/components/**/*
alwaysApply: false
---

# Component Rules

Rules for developing UI components. Based on component-guidelines.md and figma-context.md.

## Core Principles

### 1. Shadcn First

**ALWAYS check if Shadcn has an equivalent before creating custom components.**

```bash
# Check Shadcn for component
npx shadcn@latest add button --yes

# Then adapt to project structure
```

### 2. Figma Parity

**100% visual match with Figma designs** - no approximations.

- Use exact sizes, colors, spacing from Figma
- Verify using screenshots or MCP Figma tools
- Test all states (hover, focus, disabled, active)

### 3. Atomic Design

```
atoms/     → Basic components (Button, Input, Avatar, Badge)
molecules/ → Atom combinations (InputField, SearchBar, PostCard)
organisms/ → Complex features (PostFeed, UserProfile, Forms)
templates/ → Page layouts
```

## File Structure

```
src/components/atoms/Button/
├── Button.tsx           # Main component
├── Button.test.tsx      # Unit + snapshot tests
├── Button.types.ts      # Type definitions
└── index.ts             # Exports
```

## Component Template

```tsx
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/libs';

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        outline: 'border border-input bg-background hover:bg-accent',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-8 px-3 text-xs',
        lg: 'h-12 px-6',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, ...props }, ref) => (
    <button
      ref={ref}
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  )
);
Button.displayName = 'Button';

export { Button, buttonVariants };
```

## Imports

```tsx
// ✅ Correct
import { cn } from '@/libs';
import { Button } from '@/components/atoms';

// ❌ Wrong
import { cn } from 'src/lib/utils';
import { cn } from '../../libs/utils';
```

## Exports

```tsx
// src/components/atoms/index.ts
export * from './Button';
export * from './Input';
export * from './Avatar';
```

## Design System Integration

### Colors
```tsx
// ✅ Use design tokens
<div className="bg-primary text-primary-foreground" />
<div className="bg-muted text-muted-foreground" />
<div className="border-border" />

// ❌ Don't hardcode
<div style={{ backgroundColor: '#ffffff' }} />
<div className="bg-[#1a1a1a]" />
```

### Sizing
```tsx
// ✅ Follow Figma scale
<Avatar className="h-10 w-10" />  // 40px
<Avatar className="h-8 w-8" />    // 32px
<Avatar className="h-6 w-6" />    // 24px

// ❌ Arbitrary sizes
<Avatar className="h-[37px] w-[37px]" />
```

### Spacing
```tsx
// ✅ Use Tailwind spacing scale
<div className="p-4 gap-2 space-y-4" />

// ❌ Arbitrary spacing
<div className="p-[13px]" />
```

## Testing Requirements

Every component needs:

1. **Test file**: `Component.test.tsx`
2. **Sanity test**: Renders without errors
3. **Functional tests**: Click, hover, interactions
4. **Snapshot tests**: All variants/states

```tsx
// See rules/component-testing.mdc for details
describe('Button', () => {
  it('renders with default props', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button')).toBeInTheDocument();
  });
});

describe('Button - Snapshots', () => {
  it('matches snapshot for default variant', () => {
    const { container } = render(<Button>Default</Button>);
    expect(container.firstChild).toMatchSnapshot();
  });
});
```

## Migration Checklist

When migrating/creating a component:

- [ ] Check if Shadcn has equivalent
- [ ] Analyze Figma design
- [ ] Install Shadcn if available: `npx shadcn@latest add [component]`
- [ ] Move to correct atomic level
- [ ] Adapt imports to use `@/libs`
- [ ] Implement all Figma variants
- [ ] Use CVA for variant management
- [ ] Create tests (unit + snapshot)
- [ ] Update exports in `atoms/index.ts`
- [ ] Test build: `npm run build`
- [ ] Test in browser

## Common Pitfalls

### Ref Forwarding
```tsx
// ✅ Forward refs correctly
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => <button ref={ref} {...props} />
);

// ❌ Missing forwardRef
const Button = (props: ButtonProps) => <button {...props} />;
```

### Test Queries
```tsx
// ✅ Use data-testid for reliable queries
<Button data-testid="submit-btn">Submit</Button>
const button = screen.getByTestId('submit-btn');

// ⚠️ Role queries may not work for all components
const button = screen.getByRole('button'); // May be fragile
```

### Export Updates
```tsx
// ✅ Remember to update atoms/index.ts
export * from './NewComponent';

// ❌ Forget to export = import errors
```

## Quick Checklist

When creating/modifying components:

- [ ] Shadcn checked first?
- [ ] File structure follows pattern?
- [ ] Using `@/` import aliases?
- [ ] Design tokens (not hardcoded colors)?
- [ ] Figma sizing/spacing matched?
- [ ] Tests created (unit + snapshot)?
- [ ] Exported in index.ts?
- [ ] Build passes?

---

**Reference**: `.cursor/docs/component-guidelines.md`, `.cursor/docs/figma-context.md`