Стилизация
Документация по системе стилизации в приложении, настройке цветовой схемы и оформлению компонентов.
Обзор
Boilerplate использует Tailwind CSS для стилизации и построен на основе компонентов Shadcn UI. Система стилизации базируется на:
- Tailwind CSS для утилитарных классов и отзывчивого дизайна
- CSS-переменных для определения цветовой схемы
- Компонентов Shadcn UI для унифицированного внешнего вида
- Поддержки светлой и тёмной темы
Основные файлы
Система стилизации основана на следующих ключевых файлах:
globals.css
- глобальные стили и CSS-переменныеtailwind.config.js
- конфигурация Tailwind CSSlib/utils.ts
- утилиты для работы с классамиcomponents.json
- конфигурация компонентов Shadcn UI
Цветовая схема
Цветовая схема определяется через CSS-переменные в файле globals.css
. Приложение поддерживает светлую и тёмную темы с различными наборами цветов.
Основные цветовые переменные
В корне определены следующие основные CSS-переменные:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--radius: 0.625rem;
}
Тёмная тема
Для тёмной темы определены переопределения CSS-переменных:
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
}
Изменение цветовой схемы
Для изменения цветовой схемы можно отредактировать переменные в файле globals.css
. Например, чтобы изменить основной цвет на синий:
:root {
/* Другие переменные */
--primary: oklch(0.5 0.2 240); /* Синий цвет */
--primary-foreground: oklch(0.985 0 0);
}
Настройка Tailwind
Настройка Tailwind выполняется в файле tailwind.config.js
. Конфигурация включает подключение CSS-переменных к цветам Tailwind:
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
// Другие цвета
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
// Другие настройки
},
},
plugins: [require("tailwindcss-animate")],
}
Добавление новых цветов
Чтобы добавить новый цвет в палитру, выполните следующие шаги:
- Добавьте новую CSS-переменную в
globals.css
:css:root { /* Существующие переменные */ --success: oklch(0.6 0.2 140); --success-foreground: oklch(0.985 0 0); } .dark { /* Существующие переменные */ --success: oklch(0.5 0.2 140); --success-foreground: oklch(0.985 0 0); }
- Добавьте цвет в конфигурацию Tailwind:javascript
// tailwind.config.js module.exports = { theme: { extend: { colors: { // Существующие цвета success: { DEFAULT: "hsl(var(--success))", foreground: "hsl(var(--success-foreground))", }, }, }, }, }
Переключение темы
Для добавления переключения между светлой и тёмной темой создайте компонент переключателя:
// components/ThemeToggle.tsx
"use client";
import { useState, useEffect } from "react";
import { useTheme } from "next-themes";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
export function ThemeToggle() {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
// Предотвращаем гидратационные ошибки
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<div className="flex items-center space-x-2">
<Switch
id="theme-toggle"
checked={theme === "dark"}
onCheckedChange={(checked) => {
setTheme(checked ? "dark" : "light");
}}
/>
<Label htmlFor="theme-toggle">
{theme === "dark" ? "Тёмная тема" : "Светлая тема"}
</Label>
</div>
);
}
Затем добавьте провайдер темы в корневой компонент:
// app/providers.tsx
"use client";
import { ThemeProvider } from "next-themes";
import { ReactNode } from "react";
export function Providers({ children }: { children: ReactNode }) {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
);
}
И обновите корневой layout:
// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ru" suppressHydrationWarning>
<body>
<Providers>
{children}
</Providers>
</body>
</html>
);
}
Стилизация компонентов Shadcn UI
Компоненты Shadcn UI используют систему CSS-переменных и могут быть настроены для соответствия дизайну вашего приложения.
Настройка кнопок
Для настройки внешнего вида кнопок вы можете переопределить компонент Button:
// components/ui/button.tsx
"use client";
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 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
// Добавьте свой вариант
custom: "bg-blue-500 text-white hover:bg-blue-600",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
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 };
Затем вы можете использовать новый вариант:
import { Button } from "@/components/ui/button";
export function MyComponent() {
return (
<Button variant="custom">Мой стилизованный кнопка</Button>
);
}
Стилизация форм
Для стилизации форм используйте компоненты из Shadcn UI, которые поддерживают все настройки темы:
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
// Определение схемы формы
const formSchema = z.object({
email: z.string().email("Введите корректный email"),
password: z.string().min(8, "Пароль должен содержать не менее 8 символов"),
});
export function LoginForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: "",
password: "",
},
});
function onSubmit(values: z.infer<typeof formSchema>) {
// Обработка отправки формы
console.log(values);
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
placeholder="example@example.com"
{...field}
// Дополнительные стили
className="bg-muted/50 border-border/50 focus:border-primary"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Пароль</FormLabel>
<FormControl>
<Input
type="password"
placeholder="********"
{...field}
// Дополнительные стили
className="bg-muted/50 border-border/50 focus:border-primary"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">Войти</Button>
</form>
</Form>
);
}
Кастомизация стилей полей формы
Для изменения стилей полей формы можно либо переопределить компонент, либо добавить классы через className
:
// Переопределение компонента Input
// components/ui/input.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
// Дополнительные стили по умолчанию
"transition-colors focus:border-primary",
className
)}
ref={ref}
{...props}
/>
);
}
);
Input.displayName = "Input";
export { Input };
Утилита cn
Для объединения классов Tailwind используется утилита cn
из lib/utils.ts
:
// lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Эта утилита позволяет объединять классы, в том числе условно:
import { cn } from "@/lib/utils";
export function MyComponent({ isActive }: { isActive: boolean }) {
return (
<div
className={cn(
"p-4 rounded-md transition-colors",
isActive
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground hover:bg-muted/80"
)}
>
Мой компонент
</div>
);
}
Адаптивный дизайн
Tailwind CSS предоставляет готовые брейкпоинты для адаптивного дизайна:
sm
- 640px и вышеmd
- 768px и вышеlg
- 1024px и вышеxl
- 1280px и выше2xl
- 1536px и выше
Пример использования адаптивных классов:
<div className="flex flex-col md:flex-row md:space-x-4">
<div className="w-full md:w-1/2 lg:w-1/3 p-4">
<h3 className="text-xl md:text-2xl">Заголовок</h3>
<p className="text-sm md:text-base">Содержимое</p>
</div>
<div className="w-full md:w-1/2 lg:w-2/3 p-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Содержимое сетки */}
</div>
</div>
</div>
Рекомендации
- Используйте компоненты Shadcn UI для обеспечения единообразия интерфейса
- Следуйте подходу "mobile-first" при разработке с Tailwind
- Избегайте прямого изменения CSS файлов, используйте переменные и Tailwind
- Для повторяющихся комбинаций стилей создавайте компоненты или классы в
globals.css
- Используйте утилиту
cn
для объединения классов и условного применения стилей - При необходимости кастомизации темы, редактируйте CSS-переменные в
globals.css