Что такое shadcn/ui и как использовать: полный гайд 2026

Что такое 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.