Установка та Налаштування shadcn/ui
Установка та Налаштування shadcn/ui
Тепер, коли ви розумієте філософію shadcn/ui, час перейти від теорії до практики. У цій главі ми встановимо shadcn/ui, налаштуємо проєкт та додамо перші компоненти.
Що ми зробимо:
- Встановимо shadcn/ui у Next.js проєкт
- Альтернативно: встановимо у Vite + React проєкт
- Розберемо кожен крок установки
- Налаштуємо
components.json - Додамо перший компонент
- Навчимося troubleshooting типових проблем
Prerequisites (Що треба знати заздалегідь)
Перед початком переконайтеся, що ви розумієте ці технології:
1. React Fundamentals
- Components, Props, State
- Hooks (useState, useRef, useEffect)
- Event handling
Якщо ці терміни вам незн айомі, спочатку вивчіть базовий React.
2. TypeScript (Рекомендовано)
shadcn/ui написаний на TypeScript. Хоча можна використовувати з JavaScript, ви втаратите:
- Type safety
- Автокомпліт у VSCode
- Безпеку рефакторингу
3. Tailwind CSS
shadcn/ui heavily використовує Tailwind. Ви маєте розуміти:
- Utility classes (
flex,bg-primary,hover:) - Responsive prefixes (
sm:,md:,lg:) - Базову концепцію
Якщо ви не знайомі з Tailwind, рекомендую спочатку пройти їхній tutorial.
4. Node.js та Package Manager
- Node.js v18+ встановлений
- npm, pnpm або yarn
Перевірте версію:
node -v # v18.0.0 або вище
npm -v # 9.0.0 або вище
Варіант 1: Установка з Next.js (Рекомендовано)
shadcn/ui ідеально інтегрується з Next.js (особливо App Router). Це найпростіший варіант.
Крок 1: Створення Проєкту та Ініціалізація shadcn/ui
Запустіть init команду — вона створить Next.js проєкт і налаштує shadcn/ui автоматично:
npx shadcn@latest init
CLI запитає, який тип проєкту створити (Next.js або Monorepo), а потім задасть питання про налаштування:
✔ Which color would you like to use as the base color? › Zinc
Вибір базового кольору:
- Slate, Gray, Zinc, Neutral, Stone
Це базовий колір для neutral elements (borders, backgrounds, text). Zinc — універсальний вибір.
- Створює Next.js проєкт з TypeScript та Tailwind CSS
- Налаштовує
components.json - Створює
src/lib/utils.tsз функцієюcn() - Оновлює
globals.cssзі змінними теми - Налаштовує path aliases (
@/*)
Крок 2: Що створив CLI?
Після ініціалізації ваш проєкт має таку структуру:
Розберемо кожен файл:
components.json (Конфігураційний файл)
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
Поля конфігу:
.tsx файли (TypeScript).tw-). Зазвичай порожній.::
src/lib/utils.ts (Utility функція)
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Що робить cn() функція?
Проблема: Tailwind класи можуть конфліктувати:
// Який колір буде?
<div className="bg-red-500 bg-blue-500" />
// Відповідь: залежить від порядку в згенерованому CSS (непередбачувано!)
Рішення: twMerge вирішує конфлікти:
cn('bg-red-500', 'bg-blue-500')
// Результат: "bg-blue-500" (останній виграє)
А clsx? Зручний синтаксис для conditional classes:
cn('button', isActive && 'active', isPrimary ? 'primary' : 'secondary', className)
Використання в компонентах:
<Button className={cn('base-classes', variant === 'default' && 'default-classes', className)} />
cn(). Не видаляйте цей файл!Детальний Розбір: Як Працює cn()
Розберемо цю функцію по частинах, бо вона критична для розуміння shadcn/ui.
Проблема 1: Конфлікти Tailwind класів
// Уявіть, що ви передаєте кастомні класи в компонент:
<Button className="bg-red-500">Delete</Button>
// Але компонент вже має базовий bg-primary
// Який колір буде в результаті?
Відповідь: непередбачувано! В Tailwind CSS, коли є два класи що встановлюють одну і ту саму властивість (наприклад, bg-red-500 та bg-primary), результат залежить від порядку в згенерованому CSS файлі, а не від порядку в HTML.
Рішення: tailwind-merge
import { twMerge } from 'tailwind-merge'
twMerge('bg-primary', 'bg-red-500')
// Результат: "bg-red-500" (останній виграє)
twMerge('px-4 py-2', 'px-8')
// Результат: "py-2 px-8" (px-4 видалено, px-8 залишився)
tailwind-merge інтелектуально визначає конфлікти та залишає тільки останнє значення для кожної CSS властивості.
Проблема 2: Conditional класи
// Незручний спосіб:
<div className={`button ${isActive ? 'active' : ''} ${isPrimary ? 'primary' : 'secondary'} ${className || ''}`} />
// Треба стежити за пробілами, обробляти undefined
Рішення: clsx
import { clsx } from 'clsx'
clsx('button', isActive && 'active', isPrimary ? 'primary' : 'secondary', className)
// Автоматично:
// - Фільтрує false/null/undefined
// - Додає пробіли
// - Об'єднує все в рядок
Підтримувані синтаксиси clsx:
// Рядки
clsx('foo', 'bar') // 'foo bar'
// Об'єкти (ключ додається якщо значення truthy)
clsx({ foo: true, bar: false, baz: 1 }) // 'foo baz'
// Масиви
clsx(['foo', 'bar']) // 'foo bar'
// Змішане
clsx('button', { active: isActive }, [isPrimary && 'primary']) // 'button active primary'
// Undefined/null/false ігноруються
clsx('foo', null, undefined, false, 'bar') // 'foo bar'
Комбінація: cn() = clsx() + twMerge()
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// Використання:
cn('bg-primary px-4', 'bg-red-500', isActive && 'active')
// Результат: "px-4 bg-red-500 active"
// ↑ clsx обробив conditional та об'єднав
// ↑ twMerge вирішив конфлікт bg-primary vs bg-red-500
Практичний приклад в компоненті:
// src/components/ui/button.tsx
const Button = ({ className, variant, ...props }) => {
return (
<button
className={cn(
// Базові класи (завжди присутні)
'inline-flex items-center justify-center rounded-md',
// Варіанти (з CVA)
buttonVariants({ variant }),
// Кастомні класи від користувача (можуть override)
className,
)}
{...props}
/>
)
}
// Використання:
;<Button variant="primary" className="bg-green-500">
Save
</Button>
// Результат: bg-green-500 виграє над bg-primary
className останнім аргументом в cn(). Це дозволяє користувачам компонента override будь-які стилі.CVA (Class Variance Authority): Керування Варіантами
Що це? CVA — бібліотека для створення компонентів з варіантами (variants). Вона автоматично генерує TypeScript типи та обробляє комбінації класів.
Встановлення (уже включено при npx shadcn@latest init):
npm install class-variance-authority
Базовий синтаксис:
import { cva } from 'class-variance-authority'
const buttonVariants = cva(
// Базові класи (завжди присутні)
'inline-flex items-center justify-center rounded-md transition-colors',
{
variants: {
// Варіант 1: колір
variant: {
default: 'bg-primary text-white',
destructive: 'bg-red-600 text-white',
outline: 'border border-gray-300',
},
// Варіант 2: розмір
size: {
sm: 'h-8 px-3 text-xs',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
},
},
defaultVariants: {
variant: 'default',
size: 'md',
},
},
)
// Результат: функція яка приймає варіанти та повертає класи
buttonVariants({ variant: 'destructive', size: 'lg' })
// → "inline-flex items-center justify-center rounded-md transition-colors bg-red-600 text-white h-12 px-6 text-base"
TypeScript інтеграція:
import { type VariantProps } from 'class-variance-authority'
// Автоматично генерує тип з можливими варіантами
type ButtonVariants = VariantProps<typeof buttonVariants>
// Тип:
// {
// variant?: "default" | "destructive" | "outline"
// size?: "sm" | "md" | "lg"
// }
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, ButtonVariants {
// variant та size автоматично додаються з правильними типами!
}
const Button = ({ variant, size, className, ...props }: ButtonProps) => {
return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />
}
// TypeScript автокомпліт:
<Button variant="destructive" size="lg" /> // ✅
<Button variant="invalid" /> // ❌ TypeScript error!
Compound Variants (Комбіновані варіанти):
Іноді потрібна спеціальна стилізація для певної комбінації варіантів:
const buttonVariants = cva('...', {
variants: {
variant: {
default: 'bg-primary',
outline: 'border',
},
size: {
sm: 'h-8',
lg: 'h-12',
},
},
compoundVariants: [
{
// Коли variant="outline" І size="lg"
variant: 'outline',
size: 'lg',
// Додати ці класи:
className: 'border-2', // Товстіша рамка для великих outline кнопок
},
],
})
Приклад з усіма фічами:
const alertVariants = cva(
// Базові класи
'relative w-full rounded-lg border px-4 py-3',
{
variants: {
variant: {
default: 'bg-background text-foreground',
destructive: 'border-red-500/50 text-red-600 dark:border-red-500 [&>svg]:text-red-600',
},
},
defaultVariants: {
variant: 'default',
},
},
)
// Використання в компоненті:
interface AlertProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof alertVariants> {}
const Alert = ({ variant, className, ...props }: AlertProps) => (
<div role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
)
// Без CVA (простий об'єкт):
const variants = {
primary: "bg-primary",
secondary: "bg-secondary"
}
<button className={variants[variant]} /> // Працює, але:
// ❌ Немає TypeScript автокомпліту
// ❌ Немає compound variants
// ❌ Незручно комбінувати кілька варіантів
// ❌ Немає автоматичної генерації типів
// З CVA:
// ✅ Повна TypeScript підтримка
// ✅ Compound variants
// ✅ Легко комбінувати варіанти
// ✅ Автоматична генерація VariantProps
Реальний приклад: Кастомний Badge компонент:
// src/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-md border px-2.5 py-0.5 text-xs font-semibold transition-colors',
{
variants: {
variant: {
default: 'border-transparent bg-primary text-primary-foreground',
secondary: 'border-transparent bg-secondary text-secondary-foreground',
destructive: 'border-transparent bg-destructive text-destructive-foreground',
outline: 'text-foreground',
success: 'border-transparent bg-green-500 text-white',
warning: 'border-transparent bg-yellow-500 text-white',
},
size: {
sm: 'px-2 py-0.5 text-xs',
md: 'px-2.5 py-0.5 text-sm',
lg: 'px-3 py-1 text-base',
},
},
compoundVariants: [
{
variant: 'outline',
size: 'lg',
className: 'border-2',
},
],
defaultVariants: {
variant: 'default',
size: 'md',
},
},
)
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, size, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant, size }), className)} {...props} />
}
export { Badge, badgeVariants }
Використання:
import { Badge } from '@/components/ui/badge'
function OrderStatus() {
return (
<div className="flex gap-2">
<Badge variant="success">Completed</Badge>
<Badge variant="warning" size="lg">
Pending
</Badge>
<Badge variant="destructive">Cancelled</Badge>
<Badge variant="outline" className="border-blue-500">
Custom
</Badge>
</div>
)
}
- Компонент має 2+ варіанти (колір, розмір, стан)
- Потрібна TypeScript безпека для props
- Є compound variants (спеціальні комбінації)
- Компонент має тільки один вигляд
- Стилі повністю динамічні (приходять ззовні)
- Простий wrapper без варіацій
globals.css (Стилі теми — Tailwind CSS v4)
CLI налаштовує ваш globals.css з OKLCH кольорами (сучасний формат кольорів):
@import 'tailwindcss';
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
/* ... інші dark mode кольори */
}
- Перцептуально рівномірний — однакові числові зміни дають однакові візуальні зміни
- Ширший gamut — підтримує P3 та інші сучасні кольоровие простори
- Нативна підтримка — всі сучасні браузери підтримують
oklch()
Як змінити тему?
Просто відредагуйте CSS variables:
:root {
--primary: oklch(0.5 0.2 280); /* Фіолетовий замість чорного */
}
tailwind.config.ts. Вся конфігурація (кольори, border-radius, dark mode) визначається через CSS variables у globals.css. Tailwind v4 автоматично сканує ваш проєкт та генерує потрібні стилі.Крок 3: Додавання Першого Компонента
Тепер проєкт налаштований. Додаймо кнопку:
npx shadcn@latest add button
Що відбувається:
- CLI скачує код компонента з registry
- Створює
src/components/ui/button.tsx - Автоматично встановлює залежності (якщо потрібні)
Результат:
✔ Installing dependencies.
✔ Created 1 file:
- src/components/ui/button.tsx
Крок 4: Використання Компонента
Відкрийте src/app/page.tsx:
import { Button } from '@/components/ui/button'
export default function Home() {
return (
<div>
<Button>Click me</Button>
</div>
)
}
Крок 5: Запуск Dev Server
npm run dev
Відкрийте http://localhost:3000.
Ви маєте побачити кнопку! 🎉
Варіант 2: Установка з Vite + React
Якщо ви не використовуєте Next.js, ось як встановити з Vite. Цей варіант використовує Tailwind CSS v4 з сучасним підходом без конфіг-файлів.
Крок 1: Створення Vite Проєкту
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
Крок 2: Установка Tailwind CSS v4
Tailwind CSS v4 використовує Vite плагін замість PostCSS:
npm install tailwindcss @tailwindcss/vite
postcss, autoprefixer та npx tailwindcss init.Замініть вміст src/index.css:
@import 'tailwindcss';
Переконайтесь, що src/main.tsx імпортує CSS:
import './index.css'
Крок 3: Налаштування Path Aliases
Vite не підтримує @/* aliases з коробки. Потрібна конфігурація.
1. Встановіть types для node:
npm install -D @types/node
2. Оновіть tsconfig.json:
Сучасний Vite розділяє TypeScript конфігурацію на три файли. Додайте baseUrl та paths у tsconfig.json:
{
"files": [],
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
3. Оновіть tsconfig.app.json:
{
"compilerOptions": {
// ... інші опції
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
4. Оновіть vite.config.ts:
import path from 'path'
import tailwindcss from '@tailwindcss/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
tailwindcss() додається як Vite плагін, а не через PostCSS. Це забезпечує кращу продуктивність та DX.Крок 4: Ініціалізація shadcn/ui
npx shadcn@latest init
CLI автоматично визначить Vite та Tailwind CSS:
✔ Which color would you like to use as the base color? › Neutral
CLI створить components.json, src/lib/utils.ts та оновить CSS з темою.
Крок 5: Додавання Компонентів
Аналогічно Next.js:
npx shadcn@latest add button
Крок 6: Використання
У src/App.tsx:
import { Button } from '@/components/ui/button'
function App() {
return (
<div className="flex min-h-svh flex-col items-center justify-center">
<Button>Click me</Button>
</div>
)
}
export default App
Структура Проєкту після Установки
Після setup ваш проєкт має таку структуру:
Пояснення:
components.json: Конфігурація shadcn/uisrc/components/ui/: Тут будуть ВСІ компоненти shadcn/uisrc/lib/utils.ts: Utility функції (основна —cn())
src/components/ui/ — це ваш код. Ви можете редагувати їх як завгодно. Це не node_modules.Додавання Кількох Компонентів Одразу
Можна додати кілька компонентів однією командою:
npx shadcn@latest add button card dialog input label
Або додати все, що може знадобитися (⚠️ багато файлів):
npx shadcn@latest add
# CLI покаже список всіх доступних компонентів
# Оберіть потрібні (Space для вибору, Enter для підтвердження)
Огляд Компонента: Button
Подивімось на згенерований код кнопки:
// src/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'
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
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 }
Розбір коду:
1. CVA для variants:
const buttonVariants = cva(/* базові класи */, {
variants: { /* варіації */ },
defaultVariants: { /* дефолти */ }
})
2. TypeScript типи:
export interface ButtonProps
extends
React.ButtonHTMLAttributes<HTMLButtonElement>, // Усі HTML button props
VariantProps<typeof buttonVariants> {
// variant та size props
asChild?: boolean // Radix Slot API
}
3. Radix Slot (опціонально):
<Button asChild>
<a href="/home">Home</a>
</Button>
// Рендерить <a> з стилями кнопки, а не <button>
4. forwardRef для ref передачі:
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(...)
// Можна використати ref:
const buttonRef = useRef<HTMLButtonElement>(null)
<Button ref={buttonRef} />
Кастомізація Компонента
Тепер найцікавіше: як змінювати компоненти?
Приклад 1: Додати новий variant
Відкрийте button.tsx та додайте:
const buttonVariants = cva('...', {
variants: {
variant: {
default: '...',
destructive: '...',
// ↓ Додаємо новий variant
success: 'bg-green-600 text-white shadow-sm hover:bg-green-700',
},
},
})
Використання:
<Button variant="success">Save</Button>
TypeScript автоматично додасть 'success' до автокоміта!
Приклад 2: Змінити базовий стиль
Хочете rounded-lg замість rounded-md?
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-lg ...', // ← змінили md на lg
{
/* ... */
},
)
Усі кнопки тепер мають більший border-radius.
Приклад 3: Додати іконку
Створіть wrapper компонент:
// src/components/icon-button.tsx
import { Button, ButtonProps } from '@/components/ui/button'
import { Loader2 } from 'lucide-react'
interface IconButtonProps extends ButtonProps {
icon?: React.ReactNode
loading?: boolean
}
export function IconButton({ icon, loading, children, ...props }: IconButtonProps) {
return (
<Button disabled={loading || props.disabled} {...props}>
{loading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : icon && <span className="mr-2">{icon}</span>}
{children}
</Button>
)
}
Використання:
import { Save } from 'lucide-react'
;<IconButton icon={<Save />} loading={isSubmitting}>
Save Changes
</IconButton>
Troubleshooting: Типові Проблеми
Проблема 1: "Cannot find module '@/components/ui/button'"
Причина: Path aliases не налаштовані.
Рішення для Vite:
// vite.config.ts
import path from "path"
export default define Config({
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Проблема 2: Tailwind класи не застосовуються
Причина: Tailwind CSS v4 не підключений як Vite плагін.
Рішення для Vite:
// vite.config.ts
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()], // ← Додайте tailwindcss()
})
Також переконайтесь, що в index.css є:
@import 'tailwindcss';
Проблема 3: CSS variables не працюють
Причина: Не імпортовано globals.css.
Рішення для Next.js:
// src/app/layout.tsx
import './globals.css' // ← Переконайтесь, що це є
Рішення для Vite:
// src/main.tsx
import './index.css' // ← Тут має бути @import "tailwindcss"
Проблема 4: Dark mode не працює
Причина: Клас .dark не додається на <html>.
Рішення для Next.js (використайте next-themes):
npm install next-themes
// src/app/layout.tsx
import { ThemeProvider } from 'next-themes'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
)
}
Theme toggle компонент:
'use client'
import { useTheme } from 'next-themes'
import { Button } from '@/components/ui/button'
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
return (
<Button variant="outline" onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle theme
</Button>
)
}
Підсумок: Checklist Успішної Установки
- Node.js v18+ встановлений
- Tailwind CSS v4 налаштований (
@import "tailwindcss"в CSS,@tailwindcss/viteплагін для Vite) - Path aliases працюють (
@/componentsта@/lib) -
components.jsonстворений -
src/lib/utils.tsіснує зcn()функцією - CSS файл містить OKLCH CSS variables для теми
- Перший компонент (Button) доданий та працює
- Dark mode налаштований (опціонально)
Якщо всі пункти виконані — ви готові до використання shadcn/ui! 🎉
У наступній главі ми детально розберемо базові компоненти: Button, Card, Badge, Avatar та інші фундаментальні елементи інтерфейсу.
Філософія shadcn/ui: "Not a Component Library"
Коли відкриваєш офіційний сайт shadcn/ui, перше, що бачиш — провокаційне твердження:
Базові Компоненти shadcn/ui: Фундамент Інтерфейсу
Тепер, коли проєкт налаштований, час розібрати базові компоненти shadcn/ui. Ці компоненти — фундамент будь-якого інтерфейсу. Ви використовуватимете їх щодня: кнопки, картки, бейджі, аватари.