Tailwind CSS — установка, utility-first и адаптивность
Tailwind CSS — CSS-фреймворк с подходом utility-first. Интерфейс собирается из небольших классов прямо в разметке: p-4 задаёт отступ, text-lg — размер текста, bg-blue-600 — фон, flex — Flexbox.
<button class="rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white hover:bg-blue-700">
Сохранить
</button>Tailwind сканирует исходные файлы проекта, находит используемые классы и генерирует соответствующий CSS.
О версиях. В Tailwind CSS 4 используется CSS-first-настройка:
@import "tailwindcss",@themeи автоматическое обнаружение исходников.tailwind.config.jsи полеcontentхарактерны прежде всего для Tailwind CSS 3. Ниже рассмотрены оба подхода.
Содержание
- Установка и настройка
- Utility-first подход
- Spacing: отступы и расстояния
- Color: цветовые классы
- Typography: текстовые классы
- Layout-классы
- Responsive-модификаторы
- Состояния
- Кастомизация темы в Tailwind CSS 4
- Кастомизация `tailwind.config.js`
- Компонентный подход
- `@apply`
- Dark mode
- Оптимизация бандла
- Общий пример
- Частые ошибки
- Краткая памятка
Установка и настройка
Tailwind CSS 4 с Vite
npm install tailwindcss @tailwindcss/vitevite.config.js:
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss()],
});Основной CSS-файл:
@import "tailwindcss";Подключение в JavaScript:
import "./styles.css";Запуск:
npm run devTailwind CSS 4 через CLI
npm install tailwindcss @tailwindcss/clisrc/input.css:
@import "tailwindcss";Сборка с наблюдением:
npx @tailwindcss/cli -i ./src/input.css -o ./dist/styles.css --watchРезультат подключается обычным <link>:
<link rel="stylesheet" href="./dist/styles.css">Tailwind CSS 4 через PostCSS
npm install tailwindcss @tailwindcss/postcss postcsspostcss.config.mjs:
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};CSS-файл:
@import "tailwindcss";Классическая настройка Tailwind CSS 3
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -ptailwind.config.js:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{html,js,ts,jsx,tsx,vue,svelte}",
],
theme: {
extend: {},
},
plugins: [],
};Основной CSS-файл v3:
@tailwind base;
@tailwind components;
@tailwind utilities;Для адаптивной страницы необходим viewport:
<meta name="viewport" content="width=device-width, initial-scale=1.0">Utility-first подход
Вместо отдельного класса .card стили задаются комбинацией утилит:
<article class="max-w-md rounded-2xl bg-white p-6 shadow-lg">
<h2 class="text-xl font-bold text-slate-900">Заголовок</h2>
<p class="mt-2 leading-relaxed text-slate-600">Описание карточки.</p>
</article>Преимущества:
- не нужно придумывать имя для каждого небольшого блока;
- стили находятся рядом с разметкой;
- используются значения общей дизайн-системы;
- изменения одного компонента не затрагивают случайные элементы;
- production-сборка содержит только обнаруженные утилиты.
Длинные наборы классов не обязательно переносить в CSS. Повторяемую разметку обычно выносят в компонент React, Vue, Svelte, шаблонизатора или серверного фреймворка.
Spacing: отступы и расстояния
Padding
<div class="p-4">Со всех сторон</div>
<div class="px-6 py-3">По горизонтали и вертикали</div>
<div class="pt-2 pr-4 pb-6 pl-8">Отдельно для каждой стороны</div>| Класс | Назначение |
|---|---|
p-* |
отступ со всех сторон |
px-*, py-* |
горизонтальный и вертикальный отступ |
pt-*, pr-*, pb-*, pl-* |
отдельная сторона |
Margin
<div class="m-4">Внешний отступ</div>
<div class="mx-auto max-w-5xl">Центрирование контейнера</div>
<div class="mt-8 mb-4">Вертикальные отступы</div>
<div class="-mt-2">Отрицательный отступ</div>gap и space-*
<div class="flex flex-wrap gap-4">...</div>
<div class="grid gap-x-6 gap-y-8">...</div>
<ul class="space-y-3">...</ul>Для Grid и Flexbox, особенно с переносом строк, обычно удобнее gap. space-y-* и space-x-* добавляют расстояние между непосредственными соседями.
Размеры
<div class="w-full max-w-3xl">...</div>
<div class="min-h-screen">...</div>
<div class="size-12">Одинаковые width и height</div>Произвольное значение указывается в квадратных скобках:
<div class="w-[42rem] max-w-[calc(100%-2rem)]">...</div>Если нестандартное значение повторяется, лучше добавить именованный токен в тему.
Color: цветовые классы
<div class="bg-slate-100 text-slate-900 border-slate-300">...</div>
<button class="bg-blue-600 text-white hover:bg-blue-700">Кнопка</button>
<p class="text-red-600">Ошибка</p>| Префикс | Назначение |
|---|---|
text-* |
цвет текста |
bg-* |
цвет фона |
border-* |
цвет границы |
outline-* |
цвет обводки |
ring-* |
цвет декоративного кольца |
fill-*, stroke-* |
оформление SVG |
Прозрачность добавляется после /:
<div class="bg-black/50 border-white/20">...</div>Произвольный цвет:
<div class="bg-[#1e40af] text-[rgb(255_255_255)]">...</div>Typography: текстовые классы
<h1 class="text-4xl font-extrabold tracking-tight text-slate-950">
Заголовок страницы
</h1>
<p class="mt-4 text-base leading-7 text-slate-600">
Основной текст.
</p>| Группа | Примеры |
|---|---|
| Размер | text-sm, text-base, text-xl, text-4xl |
| Насыщенность | font-normal, font-medium, font-semibold, font-bold |
| Высота строки | leading-tight, leading-normal, leading-7 |
| Интервал | tracking-tight, tracking-wide |
| Выравнивание | text-left, text-center, text-right |
| Семейство | font-sans, font-serif, font-mono |
| Переполнение | truncate, text-ellipsis, line-clamp-3 |
Адаптивный размер средствами CSS:
<h1 class="text-[clamp(2rem,5vw,4.5rem)] leading-none">...</h1>Layout-классы
Flexbox:
<nav class="flex items-center justify-between gap-4">
<a class="font-bold" href="#">Логотип</a>
<div class="flex items-center gap-3">...</div>
</nav>Полезные классы: flex, flex-col, flex-wrap, items-center, justify-between, flex-1, grow, shrink-0, basis-1/2.
Grid:
<section class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
<article>...</article>
<article>...</article>
<article>...</article>
</section>Автоматическая карточная сетка:
<section class="grid grid-cols-[repeat(auto-fit,minmax(min(100%,18rem),1fr))] gap-6">
...
</section>В произвольных CSS-значениях пробелы заменяются подчёркиваниями.
Responsive-модификаторы
Tailwind следует mobile-first. Класс без префикса действует на всех размерах. sm:, md:, lg: и другие варианты начинают действовать с соответствующей минимальной ширины.
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">...</div>| Вариант | Минимальная ширина по умолчанию |
|---|---|
sm: |
40rem — 640 px при корневом размере 16 px |
md: |
48rem — 768 px |
lg: |
64rem — 1024 px |
xl: |
80rem — 1280 px |
2xl: |
96rem — 1536 px |
sm: означает «от ширины sm», а не «только смартфон». Мобильные стили задаются без префикса:
<aside class="hidden md:block">...</aside>Диапазон и произвольная точка:
<div class="md:max-lg:flex">Только в диапазоне md–lg</div>
<div class="max-md:hidden">Скрыто ниже md</div>
<div class="min-[900px]:grid-cols-3">...</div>Состояния
<button
class="bg-blue-600 px-4 py-2 text-white
hover:bg-blue-700
focus:outline-none focus-visible:ring-4 focus-visible:ring-blue-300
active:translate-y-px
disabled:cursor-not-allowed disabled:opacity-50"
>
Сохранить
</button>Частые варианты: hover:, focus:, focus-visible:, active:, visited:, disabled:, checked:, first:, last:, odd:, even:, dark:, motion-reduce:.
Варианты комбинируются:
<a class="text-blue-700 md:hover:text-blue-900 dark:text-blue-300">Ссылка</a>group
Позволяет изменить потомка при состоянии родителя:
<a class="group block rounded-xl p-4 hover:bg-slate-100" href="#">
<h2 class="font-semibold group-hover:text-blue-600">Документация</h2>
<span class="inline-block group-hover:translate-x-1">Подробнее →</span>
</a>peer
Позволяет учитывать состояние соседнего элемента:
<label class="block">
<span>Email</span>
<input class="peer mt-1 w-full rounded-md border p-2" type="email" required>
<span class="hidden text-sm text-red-600 peer-invalid:block">
Введите корректный адрес
</span>
</label>Элемент с peer-* размещается после элемента с классом peer.
Кастомизация темы в Tailwind CSS 4
В v4 дизайн-токены настраиваются через @theme:
@import "tailwindcss";
@theme {
--color-brand-50: #eff6ff;
--color-brand-600: #2563eb;
--color-brand-700: #1d4ed8;
--font-display: "Manrope", system-ui, sans-serif;
--font-body: "Inter", system-ui, sans-serif;
--breakpoint-xs: 30rem;
--breakpoint-3xl: 120rem;
--spacing-18: 4.5rem;
--radius-card: 1.25rem;
}Использование:
<section class="rounded-card bg-brand-50 p-18 xs:p-6">
<h2 class="font-display text-brand-700">Заголовок</h2>
</section>Пространства имён --color-*, --font-*, --breakpoint-*, --spacing-* и --radius-* создают связанные utility-классы или варианты. Эти значения одновременно являются обычными CSS custom properties.
Кастомизация tailwind.config.js
В Tailwind CSS 3 тему обычно расширяют через theme.extend:
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx,vue,svelte}"],
darkMode: "class",
theme: {
extend: {
colors: {
brand: {
50: "#eff6ff",
600: "#2563eb",
700: "#1d4ed8",
},
},
fontFamily: {
display: ["Manrope", "system-ui", "sans-serif"],
body: ["Inter", "system-ui", "sans-serif"],
},
screens: {
xs: "480px",
"3xl": "1920px",
},
spacing: {
18: "4.5rem",
},
borderRadius: {
card: "1.25rem",
},
},
},
plugins: [],
};extend дополняет стандартную тему. Например, прямое определение theme.colors заменяет стандартную палитру, а theme.extend.colors сохраняет её и добавляет новые значения.
JavaScript-конфигурацию можно подключить в Tailwind CSS 4 для совместимости:
@config "../tailwind.config.js";
@import "tailwindcss";Для нового проекта v4 предпочтительнее @theme, если его возможностей достаточно.
Компонентный подход
Повторяемый блок лучше вынести в компонент приложения:
export function Button({ children, variant = "primary", ...props }) {
const variants = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-slate-200 text-slate-900 hover:bg-slate-300",
};
return (
<button
className={`rounded-lg px-4 py-2 font-semibold ${variants[variant]}`}
{...props}
>
{children}
</button>
);
}Не собирайте имена классов из частей:
// Плохо: сканер может не увидеть итоговый класс
<div className={`bg-${color}-600`}>...</div>Используйте полные статические строки:
const variants = {
blue: "bg-blue-600 hover:bg-blue-700",
red: "bg-red-600 hover:bg-red-700",
};
<div className={variants[color]}>...</div>@apply
@apply переносит набор утилит в собственный CSS-класс:
.button {
@apply inline-flex items-center justify-center rounded-lg px-4 py-2;
@apply font-semibold transition-colors;
@apply focus:outline-none focus-visible:ring-4;
}
.button--primary {
@apply bg-blue-600 text-white hover:bg-blue-700;
@apply focus-visible:ring-blue-300;
}<button class="button button--primary">Сохранить</button>@apply уместен для сторонней разметки, повторяемого публичного CSS API или постепенного внедрения Tailwind. Не стоит создавать отдельный класс для каждой одноразовой комбинации: это уменьшает пользу utility-first. В приложении повторение чаще устраняется компонентами.
Пользовательские компоненты можно разместить в слое:
@layer components {
.prose-link {
@apply font-medium text-blue-700 underline underline-offset-4;
@apply hover:text-blue-900;
}
}Dark mode
Используйте вариант dark::
<article class="bg-white text-slate-900 dark:bg-slate-900 dark:text-slate-100">
<h2 class="text-slate-950 dark:text-white">Заголовок</h2>
<p class="text-slate-600 dark:text-slate-300">Описание</p>
</article>В Tailwind CSS 4 по умолчанию вариант ориентируется на prefers-color-scheme.
Для ручного переключения классом .dark:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));const root = document.documentElement;
const button = document.querySelector("[data-theme-toggle]");
button.addEventListener("click", () => {
root.classList.toggle("dark");
localStorage.setItem(
"theme",
root.classList.contains("dark") ? "dark" : "light"
);
});Восстановление настройки:
const saved = localStorage.getItem("theme");
const systemDark = matchMedia("(prefers-color-scheme: dark)").matches;
if (saved === "dark" || (!saved && systemDark)) {
document.documentElement.classList.add("dark");
}Код начального определения темы желательно выполнить в <head>, чтобы избежать вспышки неверной темы.
Для атрибута вместо класса:
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));Оптимизация бандла
В старой терминологии удаление неиспользуемых классов называли purge. Tailwind CSS 3 сканирует пути из content:
export default {
content: [
"./index.html",
"./src/**/*.{html,js,ts,jsx,tsx,vue,svelte}",
],
};В Tailwind CSS 4 типичные исходники обнаруживаются автоматически. Tailwind рассматривает их как обычный текст: он не выполняет JavaScript и не вычисляет интерполяцию строк. Поэтому имена классов должны находиться в файлах целиком.
// Ненадёжно
`text-${size}`
// Надёжно
const sizes = {
small: "text-sm",
medium: "text-base",
large: "text-lg",
};Дополнительный источник в v4:
@import "tailwindcss";
@source "../shared-components";Полностью явная настройка источников:
@import "tailwindcss" source(none);
@source "../src";
@source "../packages/ui";Точечный safelist:
@source inline("bg-red-500 text-white hover:bg-red-700");Практические правила:
- используйте production-сборку проекта;
- не составляйте классы из динамических фрагментов;
- включайте шаблоны всех компонентов в область сканирования;
- не сканируйте без причины
node_modulesи каталоги сборки; - не добавляйте большой safelist «на всякий случай»;
- храните варианты в таблицах полных статических классов.
Общий пример
<article
class="group overflow-hidden rounded-2xl bg-white shadow-lg ring-1 ring-black/5
transition hover:-translate-y-1 hover:shadow-xl
dark:bg-slate-900 dark:ring-white/10"
>
<img
class="aspect-[4/3] w-full object-cover sm:aspect-video"
src="product.jpg"
alt="Описание товара"
>
<div class="p-4 sm:p-6">
<span class="rounded-full bg-blue-50 px-2.5 py-1 text-xs font-semibold
text-blue-700 dark:bg-blue-950 dark:text-blue-300">
Новинка
</span>
<h2 class="mt-3 text-xl font-bold tracking-tight text-slate-950
group-hover:text-blue-700 sm:text-2xl
dark:text-white dark:group-hover:text-blue-300">
Название товара
</h2>
<p class="mt-2 line-clamp-3 leading-7 text-slate-600 dark:text-slate-300">
Краткое описание товара с его главными преимуществами.
</p>
<button class="mt-6 w-full rounded-lg bg-blue-600 px-4 py-2.5 font-semibold
text-white hover:bg-blue-700 focus:outline-none
focus-visible:ring-4 focus-visible:ring-blue-300
disabled:cursor-not-allowed disabled:opacity-50"
type="button">
Добавить в корзину
</button>
</div>
</article>Сетка карточек:
<section class="mx-auto grid w-[min(100%-2rem,80rem)]
grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<!-- Карточки -->
</section>Частые ошибки
sm: воспринимается как «мобильный стиль»
<!-- Мобильное значение должно быть без префикса -->
<div class="text-sm lg:text-xl">...</div>Удаляется видимый фокус
<!-- Нужна доступная замена outline -->
<button class="focus:outline-none focus-visible:ring-4">...</button>Слишком много произвольных значений
<div class="p-[17px] mt-[23px] rounded-[11px]">...</div>Повторяющиеся значения следует перенести в тему.
Смешиваются инструкции v3 и v4
Для v4 типичны:
@import "tailwindcss";
@theme { ... }Для v3 типичны @tailwind base, @tailwind components, @tailwind utilities и поле content в tailwind.config.js.
Краткая памятка
Отступы: p-4 px-6 py-2 m-4 mx-auto mt-8 gap-4
Размеры: w-full max-w-5xl min-h-screen size-12
Цвет: bg-blue-600 text-white border-slate-300 bg-black/50
Текст: text-lg font-semibold leading-7 tracking-tight
Flexbox: flex flex-col items-center justify-between gap-4
Grid: grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3
Оформление: rounded-xl border shadow-lg ring-1
Responsive: sm: md: lg: xl: 2xl: max-md: min-[900px]:
Состояния: hover: focus: focus-visible: active: disabled:
Связи: group group-hover: peer peer-invalid:
Тёмная тема: dark:bg-slate-900 dark:text-whiteГлавный принцип: сначала соберите интерфейс из стандартных утилит, затем вынесите повторяемую разметку в компоненты и только после этого добавляйте токены, собственный CSS или @apply, когда для них есть практическая причина.
Официальная документация: установка, utility-классы, адаптивность, тема, dark mode, обнаружение классов.