Урок 03. Теория: сканирование, тема, слои

📁 Раздел: Введение ⏱️ Время изучения: ~50 мин 🎯 Сложность: Начальная

⚡ Кратко

Сканер читает файлы проекта как текст и собирает всё, что похоже на имя утилиты. Генератор превращает найденное в CSS-правила, подставляя значения из темы. Тема — это набор CSS-переменных в @theme, и каждая переменная порождает семейство утилит. Результат раскладывается по слоям, поэтому утилиты всегда сильнее базовых стилей, но слабее ваших собственных правил, написанных вне слоёв.

Фаза 1. Сканирование

Tailwind обходит дерево проекта от корня и читает файлы как обычный текст. Он не разбирает JavaScript, не выполняет шаблоны и не строит DOM — он ищет подстроки, похожие на имена утилит. Пропускаются каталоги из .gitignore (в том числе node_modules) и бинарные файлы.

Из этого следует правило, которое стоит запомнить раз и навсегда:

Имя класса должно целиком встречаться в исходнике. Никакой склейки из переменных, никакой конкатенации — иначе сканеру нечего найти.

Сканер похож на корректора, который ищет в рукописи упоминания городов, чтобы напечатать для книги только нужные карты. Он читает буквы. Если в тексте написано «столица» вместо «Париж», карту Парижа он не напечатает — догадываться не его работа.

Как расширить область сканирования

Иногда разметка живёт там, куда автоопределение не заглядывает: в пакете из node_modules, в каталоге, игнорируемом гитом, в шаблонах бэкенда. Тогда источники указывают явно:

@import "tailwindcss";

/* добавить каталог к сканированию */
@source "../node_modules/@my-company/ui-kit/dist";

/* исключить каталог */
@source not "./src/legacy";

/* сгенерировать классы, которых нет в коде (например, приходят с сервера) */
@source inline("underline font-bold text-emerald-600");

Есть и обратный приём: @import "tailwindcss" source(none); полностью отключает автоопределение — тогда сканируется только то, что вы перечислили сами. Так собран и CSS для демонстраций этого курса.

Фаза 2. Генерация: откуда берутся значения

Найдя p-4, движок не ищет готовое правило в библиотеке — он вычисляет его из темы:

/* тема задаёт базовый шаг */
--spacing: 0.25rem;

/* утилита получается умножением */
.p-4 { padding: calc(var(--spacing) * 4); }

То же и с цветами: bg-slate-900 подставляет значение переменной --color-slate-900. Поэтому изменение темы меняет сразу все утилиты, которые из неё выведены.

Фаза 3. Слои и сила правил

Готовый CSS раскладывается по каскадным слоям в фиксированном порядке:

@layer theme, base, components, utilities;
  • theme — переменные темы;
  • base — preflight: сброс отступов, box-sizing, списки без маркеров, изображения как блочные элементы;
  • components — место для ваших компонентных классов;
  • utilities — утилиты.

Слои решают вечную проблему специфичности: правило из utilities всегда сильнее правила из base, даже если селектор base выглядит «сильнее». А любой ваш CSS, написанный вне слоёв, окажется сильнее утилит — и это самая частая причина «мой класс перебивает Tailwind».

💡 На заметку: если нужно, чтобы ваш CSS не спорил с утилитами, пишите его в @layer components — тогда утилиты смогут его переопределить, как и задумано.

Тема: одна переменная — семейство утилит

Блок @theme — это не «настройки», а объявление токенов. Каждое пространство имён отвечает за свою группу утилит:

/* src/style.css */
@import "tailwindcss";

@theme {
  --color-brand-500: oklch(0.55 0.21 264);
  --spacing-huge: 7.5rem;
  --breakpoint-tablet: 60rem;
}

После такой темы в проекте появляются bg-brand-500, text-brand-500, border-brand-500, ring-brand-500, отступ p-huge и вариант tablet: — без единой строчки дополнительного кода. Заодно значения доступны как обычные CSS-переменные: var(--color-brand-500).

Вот тот же приём, применённый в этом курсе: у демонстраций есть собственный токен --brand, и он работает как обычный цвет темы.

bg-brand border-brand text-brand
<div class="flex flex-wrap items-center gap-3">
  <span class="rounded-md bg-brand px-3 py-1.5 text-sm font-medium text-brand-foreground">bg-brand</span>
  <span class="rounded-md border border-brand px-3 py-1.5 text-sm text-brand">border-brand</span>
  <span class="text-sm font-semibold text-brand">text-brand</span>
</div>

Сброс и замена шкал

Тема не только дополняется, но и урезается. Запись со звёздочкой очищает целое пространство имён — удобно, когда в проекте должна остаться только фирменная палитра:

@theme {
  --color-*: initial;          /* убрать всю палитру по умолчанию */
  --color-white: #fff;
  --color-ink: oklch(0.2 0 0);
}

После этого bg-red-500 просто не существует — движок его не сгенерирует. Это осознанный приём для дизайн-систем: «нельзя случайно использовать цвет вне палитры».

Как посмотреть, что получилось

  1. DevTools → Styles: выберите элемент и увидите готовое правило утилиты вместе с подставленными значениями.
  2. Поиск по итоговому CSS: соберите файл и найдите в нём класс — если его нет, значит, сканер его не увидел.
  3. Вкладка Computed: показывает финальное значение свойства, если несколько правил спорят между собой.
⚠️ Проверить по документации: набор пространств имён темы (--color-*, --spacing, --font-*, --text-*, --radius-*, --shadow-*, --breakpoint-*, --container-*, --ease-*, --animate-*) со временем пополняется — актуальный список смотрите в разделе Theme variables документации.