Отказ от Tailwind: как структурировать CSS без фреймворка

Джулия Эванс восемь лет работала с Tailwind, а потом перевела несколько сайтов на ванильный CSS. Разбираем её систему: компоненты, переменные, Grid без media queries.

Обложка: Отказ от Tailwind: как структурировать CSS без фреймворка

Восемь лет назад Джулия Эванс открыла для себя Tailwind и была в восторге. Теперь она провела неделю, переводя несколько сайтов обратно на семантический HTML и ванильный CSS — и делится практической системой из девяти составных частей: reset, компоненты, цвета, размеры шрифтов, утилиты, базовые стили, отступы, адаптивный Grid и сборка через esbuild.

Ключевые выводы

Tailwind обучает системам: reset, цветовая палитра, шкала размеров — всё это переносится в CSS custom properties без зависимостей.

Компонентный подход (один класс — один CSS-файл) резко снижает когнитивную нагрузку: 100 строк компонента — это только 100 строк.

CSS Grid с auto-fit и minmax() создаёт гибкие сетки без единого media query.

В dev-режиме сборщик не нужен: браузер понимает нативные @import и вложенные селекторы. Для production — одна команда esbuild.

Tailwind v2 без сборщика весит 2,8 МБ (270 КБ gzip) в каждом проекте — и это один из аргументов для миграции.

Зачем уходить с Tailwind

Прежде чем разбирать систему, — почему вообще стоит её строить, если Tailwind под рукой.

  • Build-система стала обязательной. Tailwind v3+ требует JIT-компилятора. Без сборщика вы застряли на v2 — а это 2,8 МБ CSS (270 КБ gzip) в каждом проекте.
  • Смесь стилей неудобна. Когда в одном проекте живут и ванильный CSS, и Tailwind — сопровождение становится болезненным.
  • Tailwind ограничивает. grid-template-areas технически работает через arbitrary values, но синтаксис настолько громоздкий, что на практике им никто не пользуется. Container queries, @layer, @scope — туда же.
  • Накопленный опыт. После нескольких лет работы ванильный CSS уже не пугает.
  • Ценность CSS-экспертизы. Джулия упоминает эссе «Tailwind and the Femininity of CSS»: Tailwind косвенно транслирует идею, что CSS — не настоящее программирование. Она больше не хочет поддерживать этот нарратив.

При этом Tailwind честно обучал хорошим системам. Миграция — не отказ от этих систем, а их присвоение в виде нативного CSS.

Что Tailwind успел дать

Приступая к миграции, Джулия поняла: у неё уже есть многое из нужного. Любая CSS-кодовая база состоит из нескольких слоёв — раскладка, шрифты, цвета, переиспользуемые компоненты. Для каждого нужна система, иначе хаос. Tailwind эти системы давал: reset-стили, цветовую палитру, шкалу размеров шрифтов. Переход — это не изобретение колеса заново, а перенос уже понятых систем в нативный CSS.

Девять аспектов структуры CSS

Как Джулия организует свою CSS-кодовую базу сегодня.

1. Reset-стили

Простейшее решение — скопировать первые ~200 строк Tailwind Preflight из tailwind.css. За годы работы привыкаешь к box-sizing: border-box и line-height: 1.5 как к умолчаниям. Перенос preflight сохраняет привычное поведение без npm-зависимости.

			* { box-sizing: border-box; }
html { line-height: 1.5; }
		

2. Компоненты

Это ядро подхода. Идея та же, что в Vue или React-компонентах — но без JavaScript. Три правила:

  • У каждого компонента — уникальный CSS-класс.
  • CSS одного компонента никогда не перекрывает стили другого.
  • Каждый компонент живёт в собственном CSS-файле.

При редактировании ста строк компонента нужно думать только о ста строках. Нативные вложенные CSS-селекторы (поддерживаются всеми браузерами с 2023 года) делают код компактным:

			.zine {
  /* базовые стили */

  &.horizontal {
    /* горизонтальная ориентация */
  }

  &:hover {
    /* состояние при наведении */
  }
}
		

3. Цвета

Одно правило: все цвета сайта объявляются в colours.css через CSS custom properties — никакого хардкода в компонентах.

			:root {
  --pink: #fea0c2;
  --red: #f91a55;
  --orange: rgb(222, 117, 31);
}
		

4. Размеры шрифтов

Вместо классов text-lg — переменные, взятые из Tailwind. Не нужно помнить, в каких единицах задавать размер (em, px или rem):

			:root {
  --size-xs: 0.75rem;   --line-height-xs: 1rem;
  --size-sm: 0.875rem;  --line-height-sm: 1.25rem;
  --size-base: 1rem;    --line-height-base: 1.5rem;
  --size-lg: 1.125rem;  --line-height-lg: 1.75rem;
}
		
			h3 {
  font-size: var(--size-lg);
  line-height: var(--line-height-lg);
}
		

5. Утилитарные классы

Небольшой файл для вещей, которые встречаются в разных компонентах — кнопки, .sr-only для скринридеров и т.п. Держится маленьким намеренно.

6. Базовые стили

Стили, применяемые ко всему сайту. Джулия намеренно ограничивает этот файл двумя правилами — и планирует пополнять его снизу вверх, когда паттерн из компонентов повторяется несколько раз:

			/* 950px-колонка по центру каждой секции */
section {
  --inner-width: 950px;
  padding: 3rem max(1rem, (100% - var(--inner-width))/2);
}

a {
  color: var(--orange);
}
		

7. Отступы

Наименее завершённая часть системы. Основной принцип: за внешние отступы отвечает родительский контейнер, а не сами компоненты. Паттерн «owl selector» (или «lobotomized owl» — селектор вида * + *) даёт отступ между любыми соседними дочерними элементами, не трогая первый:

			section > *+* {
  margin-top: 1rem;
}
		

Дополняет это принцип «no outer margin»: компоненты не задают собственные внешние отступы — ими управляет исключительно родитель. Это предотвращает коллизии при вставке компонента в разные контексты.

8. Адаптивный дизайн: больше Grid

Вместо Tailwind-классов вида md:text-xl — гибкие сетки CSS Grid, которые сами адаптируются без брейкпоинтов. auto-fit — директива Grid, которая создаёт столько колонок, сколько умещается в контейнер (может быть 4, 3, 2 или 1 — в зависимости от ширины):

			.card-grid {
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(100%, 400px), max-content)
  );
  justify-content: center;
}
		

Ещё одна возможность, неудобная в Tailwind, — grid-template-areas (именованные зоны сетки): в Tailwind её можно задать через arbitrary values, но синтаксис настолько громоздкий, что в реальных проектах это нечитаемо. В ванильном CSS это просто:

			.layout {
  display: grid;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
}
		

9. Система сборки: esbuild

В dev-режиме сборщик не нужен совсем: современный CSS поддерживает нативные @import и вложенные селекторы в браузере без препроцессоров.

			@import "reset.css";
@import "typography.css";
@import "colors.css";
		

Для production-бандла Джулия использует esbuild — сборщик на Go, распространяемый как нативный бинарник (без Node.js в runtime), который работает в десятки раз быстрее webpack:

			esbuild style.css \
  --bundle \
  --loader:.svg=dataurl \
  --loader:.woff2=file \
  --outfile=/tmp/out.css
		

Возможности CSS, которые стоит изучить

В процессе миграции Джулия наткнулась на несколько возможностей CSS, которые пока не использовала:

  • @layer — каскадные слои для управления приоритетом стилей без повышения специфичности.
  • @scope — ограничение области действия CSS-правил конкретным поддеревом DOM.
  • Container queries — медиазапросы относительно размера родительского контейнера, а не viewport.
  • Subgrid — вложенные сетки, наследующие треки родительской Grid.
Часто задаваемые вопросы
1
Зачем отказываться от Tailwind, если он так популярен?

Tailwind v3+ требует build-системы, а v2 без неё весит 2,8 МБ (270 КБ gzip). Если вы накопили опыт в CSS и хотите использовать grid-template-areas, @layer или container queries без костылей — ванильный CSS с custom properties даёт ту же структуру без ограничений.

2
Что заменяет Tailwind reset при переходе?

Скопируйте первые ~200 строк файла tailwind.css (Preflight) напрямую в свой проект. Это те же reset-стили — box-sizing, line-height, нормализация — только теперь без npm-зависимости.

3
Как реализовать адаптивный дизайн без media queries?

CSS Grid с repeat(auto-fit, minmax(...)) автоматически управляет количеством колонок в зависимости от ширины контейнера. Для большинства сеток media queries не нужны. Для компонентной адаптивности — container queries.

4
Нужен ли сборщик при переходе на ванильный CSS?

В dev-режиме — нет. Браузер понимает нативные @import и вложенные селекторы без препроцессоров. Для production — esbuild: один статический Go-бинарник, одна команда, никакой конфигурации.

5
Как управлять цветами и размерами шрифтов без Tailwind-классов?

Объявите переменные (--pink, --size-lg) один раз в :root и используйте через var() в компонентах. Это те же системы, что в Tailwind — только в нативном CSS, без зависимости от фреймворка.

Итог

Главный вывод: Tailwind — это не магия, а набор систем. Reset-стили, цветовая палитра, шкала размеров шрифтов — всё это существует в нативном CSS как custom properties. Компонентный подход решает те же задачи изоляции. CSS Grid делает большинство media queries ненужными. А esbuild заменяет тяжёлый toolchain одним бинарником.

Начать можно с трёх шагов: скопировать Preflight → добавить custom properties для цветов и размеров → разбить стили по файлам-компонентам. Оригинальная статья Джулии Эванс доступна на jvns.ca.