Что такое shadcn/ui и как использовать: полный гайд
shadcn/ui — не совсем библиотека компонентов в привычном смысле. Вы не устанавливаете её как пакет и не импортируете из node_modules. Вместо этого: вы запрашиваете нужный компонент через CLI, и его исходный код появляется прямо в вашем проекте — в папке components/ui/. Этот код принадлежит вам, вы можете менять его как угодно.
В 2026 году shadcn/ui — стандарт по умолчанию для большинства Next.js-проектов, заменив Material UI и Ant Design в новых разработках. AI-инструменты (Lovable, Bolt.new, v0) генерируют код на shadcn/ui по умолчанию.
Принцип «владения кодом»
Традиционные UI-библиотеки (Material UI, Ant Design, Chakra UI) работают через npm install:
# Традиционный подход
npm install @mui/material
# Компоненты живут в node_modules — вы не контролируете их код
import Button from '@mui/material/Button'
shadcn/ui работает иначе — через CLI, который копирует исходный код компонента в ваш проект:
# shadcn/ui подход
npx shadcn@latest add button
# Файл components/ui/button.tsx появляется в ВАШЕМ проекте
import { Button } from '@/components/ui/button'
Разница принципиальная: компонент кнопки теперь в вашем репозитории. Хотите изменить padding, добавить variant, изменить анимацию — открываете файл и правите. Нет борьбы с !important и sx пропсами.
Технический стек shadcn/ui
shadcn/ui построен на трёх технологиях:
React — основа компонентов. Работает с Next.js, Vite, Remix, Astro (React-режим).
Tailwind CSS — вся стилизация через утилитарные классы. Никаких CSS-файлов, никаких styled-components.
Radix UI — headless-компоненты для сложных интерактивных элементов (Dialog, Dropdown, Tooltip, Select). Radix отвечает за доступность (ARIA), клавиатурную навигацию и логику — shadcn добавляет стили поверх.
Установка
Новый Next.js проект с нуля
# Создать Next.js проект
npx create-next-app@latest my-app --typescript --tailwind --eslint
cd my-app
# Инициализировать shadcn/ui
npx shadcn@latest init
CLI задаст несколько вопросов:
? Which style would you like to use? › Default
? Which color would you like to use as base color? › Slate
? Where is your global CSS file? › src/app/globals.css
? Would you like to use CSS variables? › yes
? Where is your tailwind.config.js located? › tailwind.config.ts
? Configure the import alias for components? › @/components
? Configure the import alias for utils? › @/lib/utils
После инициализации в проекте появится:
components/
├── ui/ ← сюда добавляются компоненты
lib/
├── utils.ts ← утилита cn() для склейки классов
tailwind.config.ts ← обновлена с shadcn-цветами
globals.css ← CSS-переменные для цветовой схемы
Добавление компонентов
# Добавить отдельный компонент
npx shadcn@latest add button
npx shadcn@latest add input
npx shadcn@latest add dialog
# Добавить сразу несколько
npx shadcn@latest add button input form label
# Посмотреть все доступные компоненты
npx shadcn@latest add --list
Популярные компоненты и примеры
Button
import { Button } from "@/components/ui/button"
export function Demo() {
return (
<div className="flex gap-4">
<Button>Default</Button>
<Button variant="destructive">Удалить</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button size="sm">Маленькая</Button>
<Button size="lg">Большая</Button>
<Button disabled>Недоступна</Button>
</div>
)
}
Form + Input + Label (с react-hook-form)
npx shadcn@latest add form input label
npm install react-hook-form @hookform/resolvers zod
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
const schema = z.object({
email: z.string().email("Введите корректный email"),
password: z.string().min(8, "Минимум 8 символов"),
})
export function LoginForm() {
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: { email: "", password: "" },
})
async function onSubmit(values: z.infer<typeof schema>) {
// логика входа
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="you@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
Войти
</Button>
</form>
</Form>
)
}
Dialog (модальное окно)
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
export function DeleteModal({ onConfirm }: { onConfirm: () => void }) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Удалить проект</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Вы уверены?</DialogTitle>
</DialogHeader>
<p className="text-muted-foreground">
Это действие нельзя отменить. Все данные будут удалены.
</p>
<div className="flex gap-2 justify-end">
<Button variant="outline">Отмена</Button>
<Button variant="destructive" onClick={onConfirm}>
Удалить
</Button>
</div>
</DialogContent>
</Dialog>
)
}
DataTable с сортировкой и фильтрацией
npx shadcn@latest add table
npm install @tanstack/react-table
// Минимальный пример DataTable
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
flexRender,
type ColumnDef,
} from "@tanstack/react-table"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
interface User { id: string; name: string; email: string }
const columns: ColumnDef<User>[] = [
{ accessorKey: "name", header: "Имя" },
{ accessorKey: "email", header: "Email" },
]
export function UsersTable({ data }: { data: User[] }) {
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
return (
<Table>
<TableHeader>
{table.getHeaderGroups().map(hg => (
<TableRow key={hg.id}>
{hg.headers.map(h => (
<TableHead key={h.id}>{flexRender(h.column.columnDef.header, h.getContext())}</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map(row => (
<TableRow key={row.id}>
{row.getVisibleCells().map(cell => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
)
}
Кастомизация: темы и цвета
Все цвета shadcn/ui определены как CSS-переменные в globals.css. Смена темы — это смена переменных:
/* globals.css — светлая тема */
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--destructive: 0 84.2% 60.2%;
--border: 240 5.9% 90%;
--radius: 0.5rem;
}
/* Тёмная тема */
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--border: 240 3.7% 15.9%;
}
Переключение тёмной/светлой темы:
npm install next-themes
// providers.tsx
'use client'
import { ThemeProvider } from 'next-themes'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
)
}
// ThemeToggle компонент
import { useTheme } from 'next-themes'
import { Button } from '@/components/ui/button'
import { Moon, Sun } from 'lucide-react'
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
<Button variant="ghost" size="icon" onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
<Sun className="h-4 w-4 dark:hidden" />
<Moon className="h-4 w-4 hidden dark:block" />
</Button>
)
}
shadcn/ui в вайбкодинге
AI-инструменты (Lovable, Bolt.new, v0.dev, Cursor) активно используют shadcn/ui потому что:
- Компоненты хорошо представлены в обучающих данных (огромное количество примеров на GitHub)
- AI генерирует код с правильными
variant,sizeи className без ошибок - v0.dev от Vercel генерирует компоненты именно на shadcn/ui
Подсказка для промптов в Cursor:
Используй shadcn/ui компоненты.
Все UI-элементы строй из компонентов в @/components/ui/.
Для форм используй react-hook-form + zod.
Стилизацию делай только через className с Tailwind.
shadcn/ui vs Material UI vs Ant Design
| Параметр | shadcn/ui | Material UI | Ant Design | |---|---|---|---| | Размер бандла | Минимальный (только то, что добавили) | Большой (~300KB) | Очень большой (~500KB) | | Кастомизация | Максимальная (код в проекте) | Сложная (sx, theme) | Средняя | | Дизайн-система | Нейтральная | Material Design | Ant Design | | TypeScript | Нативно | Да | Да | | Tailwind | Требуется | Нет | Нет | | AI-генерация | ★★★★★ | ★★★☆☆ | ★★★☆☆ | | Обновления | Вы контролируете | npm update | npm update | | Enterprise | ✓ | ✓ | ✓ |
FAQ
Нужен ли Tailwind для shadcn/ui?
Да, Tailwind CSS — обязательная зависимость. shadcn/ui использует утилитарные классы Tailwind для всей стилизации. Проект без Tailwind требует его настройки перед инициализацией shadcn.
Работает ли shadcn/ui с Vite (не Next.js)?
Да. shadcn/ui поддерживает Next.js, Vite + React, Remix, Astro и Laravel (через Inertia). При инициализации CLI спрашивает, какой фреймворк используется, и настраивает соответственно.
Как обновить компоненты shadcn/ui?
Обновление — это повторное добавление компонента с флагом перезаписи. Поскольку код в вашем проекте, обновления не происходят автоматически. Это особенность: ваши кастомизации не сбрасываются при обновлении библиотеки. Для обновления: npx shadcn@latest add button --overwrite.
Как добавить кастомный компонент в shadcn-стиле?
Создайте файл components/ui/my-component.tsx, используйте утилиту cn() для склейки классов, экспортируйте компонент. Используйте cva (class-variance-authority) для вариантов — это то же самое, что делает shadcn внутри:
import { cva, type VariantProps } from 'class-variance-authority'
const myVariants = cva('base-class', {
variants: { variant: { default: 'bg-primary', outline: 'border' } }
})
Можно ли использовать shadcn/ui в React Native?
Нет. shadcn/ui рассчитан на web с Tailwind CSS. Для React Native используйте NativeWind (Tailwind для RN) + shadcn-style компоненты из @gluestack-ui/themed или tamagui.