shadcn/ui: компоненты в вашем проекте
Добавляем исходники кнопки, карточки и диалога через CLI.
On this page
Для Next.js 16.3.8 (App Router) и React 19.3.0, Tailwind CSS 4.3.3, CLI shadcn 4.21.1. Обновлено .
shadcn/ui – набор компонентов и инструмент для добавления их исходников в проект. Вы получаете код кнопки, диалога или поля ввода и можете его изменять. Готовые компоненты помогают с поведением и доступностью интерфейса, например с управлением фокусом в диалоге. Их оформление задается классами Tailwind и токенами.
Инициализация
CLI – инструмент, которым управляют командами в терминале. В нашем случае он настраивает проект и добавляет файлы компонентов.
Команды выполняются в папке созданного Next.js проекта с Tailwind. Сохраните свои изменения в Git перед настройкой, чтобы было удобно посмотреть разницу.
bunx shadcn@latest init --base radix
bunx shadcn@latest add button input label cardCLI предложит параметры оформления и путей. Для примеров этой статьи выберите Radix – библиотеку, на которой основано поведение этих компонентов. Оставьте алиас компонентов @/components, если не выбирали другую структуру. Для компонентов здесь используется путь @/components/ui.
components.json хранит настройки CLI: куда добавлять компоненты, где находится CSS, какие используются алиасы. Это конфигурация инструмента, а не компонент React.
После установки посмотрите Git diff. CLI может добавить зависимости, функцию cn, токены и исходники компонентов. Проверьте, что app/layout.tsx подключает CSS и что в globals.css нет двух разных наборов переменных, которые задают одни и те же цвета. Цвета из предыдущего упражнения можно перенести в существующие роли shadcn/ui.
Кнопка вместо собственного оформления
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
export function TicketCounter() {
const [count, setCount] = useState(1);
return (
<div className="flex flex-wrap items-center gap-3">
<p>Билетов: {count}</p>
<Button
type="button"
variant="outline"
disabled={count === 1}
onClick={() => setCount((current) => Math.max(1, current - 1))}
>
Убрать
</Button>
<Button type="button" onClick={() => setCount((current) => current + 1)}>
Добавить
</Button>
</div>
);
}variant – параметр компонента: он выбирает предусмотренный вариант оформления. disabled отключает кнопку. Обработчик добавления остался обычной функцией React: shadcn/ui оформляет интерфейс, а состояние принадлежит вашему компоненту.
type="button" указываем явно. Если позже перенести эту кнопку внутрь формы, она не начнет случайно отправлять её.
Карточка с теми же параметрами
Замените учебную карточку, сохранив прежние props. Страница и фильтр продолжат использовать её без изменения данных:
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
type EventCardProps = { name: string; price: number };
export function EventCard({ name, price }: EventCardProps) {
return (
<Card>
<CardHeader>
<CardTitle>{name}</CardTitle>
</CardHeader>
<CardContent>
<p>{price === 0 ? "Бесплатно" : `${price} ₽`}</p>
</CardContent>
</Card>
);
}У Card уже есть роли фона, текста и границы. Сравните обе темы, затем измените общий токен: карточка должна получить новый цвет без отдельной правки компонента.
Где менять внешний вид
| Изменение | Подходящее место |
|---|---|
| Другой основной акцент во всём проекте | Токены в globals.css |
| Расположение элементов одной карточки | className карточки |
| Ещё один повторяемый вариант кнопки | Варианты в исходнике Button |
| Логика фильтра или формы | Компонент приложения, использующий UI |
Не перекрашивайте каждую кнопку отдельно, если нужно изменить общий цвет интерфейса. И не добавляйте логику афиши внутрь универсального Button: он может использоваться в других местах.
Диалог – больше, чем прямоугольник поверх страницы
Для модального окна нужны перенос в подходящее место DOM, затемнение, управление фокусом, Escape и возврат фокуса к кнопке. Добавьте готовый компонент:
bunx shadcn@latest add dialog"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function EventDetails() {
return (
<Dialog>
<DialogTrigger asChild>
<Button type="button" variant="outline">
Подробнее о кинопоказе
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Кинопоказ</DialogTitle>
<DialogDescription>
Обсуждение фильма после просмотра. Начало в 19:00.
</DialogDescription>
</DialogHeader>
<p>Приходите за десять минут до начала.</p>
</DialogContent>
</Dialog>
);
}Пример рассчитан на компоненты shadcn/ui с Radix, которые поддерживают asChild. Если выбрали другую основу компонентов, проверьте в её документации, как подключается кнопка открытия диалога. asChild позволяет использовать кнопку как триггер, не создавая кнопку внутри другой кнопки.
Импортируйте EventDetails в страницу и добавьте <EventDetails />. Проверьте Tab, Escape, возврат фокуса и узкий экран. После добавления диалога всё равно проверьте, что его содержимое помещается в окне и им удобно пользоваться.
Попробуйте: оформите счетчик, фильтр и состояния афиши. Сравните обе темы. Формы с полями и сообщениями разберем в Next-04, когда будет понятна их логика.
Документация: установка Next.js, Button, Dialog, темы.