К основному содержанию
правило

shadcn — правило для Cursor: как писать компоненты shadcn/ui

Правило для Cursor задаёт команде единый стиль работы с shadcn/ui: структура папок, типы, варианты через CVA, формы на React Hook Form и Zod. Пригодится, когда компонентов становится много.

Установить в Cursor · Вручную: положить файлы Все способы и платформы ↓
.cursor/rules/shadcn.mdc
Открыть на GitHub

Что делает shadcn

Файл .cursor/rules/shadcn.mdc подключается к Cursor и применяется ко всем файлам проекта. Внутри — семь разделов с парами «плохо» и «хорошо»: организация кода, архитектура компонентов, TypeScript и API, темизация, типовые паттерны, производительность, доступность. Агент видит эти примеры и повторяет их в новом коде.

Правило требует раскладывать доменные компоненты в components/<domain>, а переиспользуемые примитивы — в components/ui, каждый в своём файле с именем в PascalCase. Функциональные компоненты пишутся с React.forwardRef и поддержкой asChild через Radix Slot, варианты — через class-variance-authority, классы склеиваются утилитой cn. Пропсы описываются интерфейсами, формы валидируются схемами Zod, вместо any — явные типы. Тяжёлые блоки вроде диалогов и таблиц правило советует подгружать через React.lazy.

Раздел про доступность в исходнике обрывается на полуслове, так что опираться на него целиком не стоит. Ещё момент: правило жёсткое по структуре файлов, и если в проекте уже сложилась другая раскладка компонентов, оно начнёт тянуть код в свою схему — это повод либо принять её, либо переписать правило под себя.

Какие задачи решает

  • разложить компоненты по доменам и примитивам без свалки в одной папке
  • добавить forwardRef и asChild в свои кнопки и поля
  • описать варианты кнопки через CVA вместо условий в className
  • подключить Zod к формам на React Hook Form
  • убрать any из пропсов и заменить его интерфейсами

Примеры запросов агенту

  • Создай компонент Button в components/ui/Button.tsx по правилам shadcn: forwardRef, asChild, варианты через CVA
  • Перепиши форму профиля на React Hook Form и Zod, схему положи рядом с компонентом
  • Разложи текущие компоненты по components/<domain> и components/ui, каждый в отдельный файл

Как установить shadcn

Вручную: положить файлы

.cursor/rules/shadcn.mdc

Во frontmatter: description, globs, alwaysApply

По шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules

Вопросы о shadcn

Как установить правило shadcn в Cursor?

Создайте файл .cursor/rules/shadcn.mdc в корне проекта и вставьте в него содержимое правила. Cursor подхватит его автоматически.

Что делает правило shadcn?

Задаёт набор соглашений для разработки на shadcn/ui: где лежат компоненты, как описывать пропсы, как строить варианты и формы. Агент следует этим примерам при генерации кода.

С чем работает правило внутри проекта?

С React, TypeScript, Tailwind CSS, class-variance-authority, Radix Slot, Zod и React Hook Form. Если этих библиотек в проекте нет, примеры из правила придётся адаптировать.

Исходный текст

shadcn.mdc · CC0-1.0
---
description: This guide defines definitive best practices for developing with shadcn/ui, ensuring consistent code organization, robust TypeScript, optimal performance, and accessible, maintainable components.
globs: **/*
---
# shadcn Best Practices

This document outlines our team's definitive best practices for developing with `shadcn/ui`. Adhere to these guidelines for all `shadcn` component development and integration.

## 1. Code Organization and Structure

Organize components logically to reflect UI hierarchy and promote discoverability.

**Rule:** Place domain-specific components under `components/<domain>` and reusable UI primitives under `components/ui`. Each component must reside in its own PascalCase file.

❌ BAD:
```
// components/Button.tsx
// components/profile-card.tsx
// components/user-settings/index.tsx (contains multiple components)
```

✅ GOOD:
```
// components/ui/Button.tsx
// components/forms/DatePicker.tsx
// components/layout/Sidebar.tsx

// components/forms/index.ts
export * from "./DatePicker";
export * from "./Input";
```

## 2. Component Architecture

Favor functional components, composition, and explicit prop definitions.

**Rule:** Use functional components with `React.forwardRef` and `asChild` for seamless integration with Radix primitives.

❌ BAD:
```tsx
// No ref forwarding, no asChild
const Button = ({ children, onClick }) => (
  <button onClick={onClick}>{children}</button>
);
```

✅ GOOD:
```tsx
// components/ui/Button.tsx
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; // Assuming you have a cn utility

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",
        destructive: "bg-destructive text-destructive-foreground",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button";
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    );
  }
);
Button.displayName = "Button";

export { Button, buttonVariants };
```

## 3. TypeScript and API Design

Enforce strict TypeScript with clear interfaces and robust validation.

**Rule:** Use interfaces for component props. Validate form data with Zod schemas. Avoid `any` and prefer explicit types.

❌ BAD:
```typescript
// Vague props, no validation
type UserFormProps = {
  data: any;
  onSubmit: (values: any) => void;
};
```

✅ GOOD:
```typescript
// components/forms/UserForm.tsx
import { z } from "zod";

export interface UserFormProps {
  initialData?: UserFormData;
  onSubmit: (values: UserFormData) => void;
}

export const userFormSchema = z.object({
  id: z.string().optional(),
  name: z.string().min(2, "Name must be at least 2 characters."),
  email: z.string().email("Invalid email address."),
});

export type UserFormData = z.infer<typeof userFormSchema>;
```

## 4. Theming and Styling

Leverage Tailwind CSS and `class-variance-authority` (CVA) for consistent, maintainable styling.

**Rule:** Define component variants using CVA. Use the `cn` utility for conditional class merging. Centralize Tailwind configuration and design tokens.

❌ BAD:
```tsx
// Inconsistent inline styles or direct class manipulation
<button className={`p-2 ${isActive ? 'bg-blue-500' : 'bg-gray-200'}`}>
```

✅ GOOD:
```tsx
// components/ui/Badge.tsx
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const badgeVariants = cva(
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold",
  {
    variants: {
      variant: {
        default: "border-transparent bg-primary text-primary-foreground",
        secondary: "border-transparent bg-secondary text-secondary-foreground",
        outline: "text-foreground",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
);

export interface BadgeProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
  return (
    <div className={cn(badgeVariants({ variant }), className)} {...props} />
  );
}

export { Badge, badgeVariants };
```

## 5. Common Patterns and Anti-patterns

**Rule:** Use React Hook Form with Zod for all forms. Implement early returns and guard clauses for error handling.

❌ BAD:
```tsx
// Deeply nested logic, manual form state
if (data) {
  // ... many lines
  if (isValid) {
    // ... more lines
  }
}
```

✅ GOOD:
```tsx
// Early return for invalid state
if (!user) {
  return <p>User not found.</p>;
}

// React Hook Form + Zod example
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { userFormSchema, UserFormData } from "./UserForm"; // From section 3

function UserProfileForm({ initialData, onSubmit }: UserFormProps) {
  const form = useForm<UserFormData>({
    resolver: zodResolver(userFormSchema),
    defaultValues: initialData,
  });

  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      {/* Form fields */}
    </form>
  );
}
```

## 6. Performance Considerations

Optimize for fast initial loads and smooth interactions.

**Rule:** Lazy-load heavy UI sections (e.g., dialogs, data tables) using `React.lazy` or dynamic imports. Memoize expensive components and callbacks.

❌ BAD:
```tsx
// Always loads heavy component
import { BigComplexChart } from "./BigComplexChart";
function Dashboard() {
  return <BigComplexChart data={...} />;
}
```

✅ GOOD:
```tsx
// Lazy load for better initial performance
import React from "react";
const LazyBigComplexChart = React.lazy(() => import("./BigComplexChart"));

function Dashboard() {
  const [showChart, setShowChart] = React.useState(false);

  // Memoize callbacks to prevent unnecessary re-renders
  const handleToggleChart = React.useCallback(() => {
    setShowChart((prev) => !prev);
  }, []);

  return (
    <div>
      <Button onClick={handleToggleChart}>Toggle Chart</Button>
      {showChart && (
        <React.Suspense fallback={<div>Loading chart...</div>}>
          <LazyBigComplexChart data={...} />
        </React.Suspense>
      )}
    </div>
  );
}
```

## 7. Accessibility

Build inclusive UIs by leveraging Radix primitives and ARIA attributes.

**Rule:** Always use `shadcn/ui` components as they are built on Radix UI and provide excellent accessibility out-of-the-box. Ensure custom components correctly pass ARIA attributes and manage focus.

❌ BAD:
```tsx
// Custom button without ARIA attributes or proper semantics
<div role="button" onClick={...}>Click me</div>
```

✅ GOOD:
```tsx
// Leverage shadcn/ui's accessible Button
import { Button } from "@/components/ui/Button";
<Button onClick={() => alert("Action!")}>Perform Action</Button>
```

## 8. Common Pitfalls and Gotchas

Avoid these common mistakes to maintain a scalable and robust codebase.

**Rule:** Never directly modify `shadcn/ui` component files. Instead, extend them with `cn` or wrap them in higher-order components. Avoid `dangerouslySetInnerHTML`.

❌ BAD:
```tsx
// Direct modification of a shadcn component (will be overwritten by CLI updates)
// components/ui/button.tsx (modified directly)
```

```tsx
// Security vulnerability
<div dangerouslySetInnerHTML={{ __html: userProvidedContent }} />
```

✅ GOOD:
```tsx
// Extend with cn for custom styles
import { Button } from "@/components/ui/Button";
<Button className="bg-red-500 hover:bg-red-600">Custom Red Button</Button>
```

```tsx
// Sanitize and render text content safely
import DOMPurify from 'dompurify';

const sanitizedContent = DOMPurify.sanitize(userProvidedContent);
return <div className="prose" dangerouslySetInnerHTML={{ __html: sanitizedContent }} />;
// Or even better, avoid dangerouslySetInnerHTML if possible and render text directly
// return <p>{userProvidedContent}</p>;
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Похожие правила IDE

Все правила IDE по теме «Фронтенд и UI» →
tanstack-start-shadcn-tailwind официальный github Правило задаёт Copilot стек TanStack Start, Shadcn/ui и Tailwind CSS и требует валидировать внешние данные через Zod. ★ 40 418звёзд репо shadcn-component-review mattbx Проверяет готовые компоненты и раскладки на соответствие паттернам shadcn: отступы, семантические токены, состав примитивов и правила выбранного визуального стиля. CX Codex +1 635установок shadcn-ui giuseppe-trisciuoglio Скилл подсказывает агенту, как ставить и настраивать компоненты shadcn/ui: кнопки, диалоги, таблицы, формы с React Hook Form и Zod. CX Codex +1 22 400установок shadcn-layouts jwynia Помогает агенту собрать раскладку на shadcn и Tailwind так, чтобы она заработала с первого раза, и разобраться с высотой, скроллом и flex/grid. CX Codex +1 457установок shadcn-svelte huntabyte Скилл помогает работать с shadcn-svelte: добавлять компоненты, составлять интерфейсы, исправлять стили и отлаживать проекты. Нужен, если вы пишете UI на Svelte с использованием shadcn-svelte или планируете начать. CX Codex +1 6 700установок

Из того же набора69

svelte Правило для Cursor задаёт, как писать компоненты Svelte 5: руны вместо старой реактивности, типизация и ключи в списках. Пригодится, если проект уже на Svelte 5. ★ 3 579звёзд репо streamlit Правило для Cursor задаёт, как писать приложения на Streamlit: кэшировать тяжёлые операции, держать UI отдельно от логики и разбивать проект на модули. ★ 3 579звёзд репо sqlalchemy Правило для Cursor задаёт стиль SQLAlchemy 2.x: типизированные модели, сессии через контекстный менеджер, select() вместо устаревшего Query. ★ 3 579звёзд репо solidity Правило задаёт стиль и требования к Solidity-коду: фиксированный pragma, кастомные ошибки, защита от реентрантности. Пригодится, когда пишете контракты в Cursor. ★ 3 579звёзд репо sentry Правило задаёт, как инициализировать Sentry в проекте: где ставить SDK, какие переменные окружения использовать, как не сломать чужой код. ★ 3 579звёзд репо scikit-image Правило задаёт команде единый стиль работы с scikit-image: неизменяемость входных массивов, float-диапазон [0, 1], векторизация вместо циклов по пикселям. ★ 3 579звёзд репо ruby Правило для Cursor задаёт команде единый стиль Ruby: RuboCop, соглашения Rails, работа с N+1 и защита от SQL-инъекций. ★ 3 579звёзд репо riverpod Правило для Cursor задаёт команде единый стиль работы с Riverpod в Flutter: где держать провайдеры, когда ref.watch, а когда ref.read. ★ 3 579звёзд репо

Спонсоры проекта

Компании, которые помогают держать аналитику открытой