Мышление «сначала утилиты»

Что это такое

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

Почему это важно

Именование — самая медленная часть работы с CSS, и именно оно накапливает стоимость сопровождения: .card обрастает классами .card--compact и .card--with-badge, а через год уже никто не помнит, какой из одиннадцати классов карточки можно безопасно удалить. У утилитарного класса ровно одно определение и один смысл, поэтому ничто не двусмысленно и ничто не остаётся брошенным, а расплата приходит в разметке: список классов там виден на месте, и повторение способна поглотить граница компонента. Знать, где должна проходить эта граница, и есть навык; без него подход «сначала утилиты» просто переносит проблему в шаблоны.

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

Список классов и есть правило стиля

Прочитав <div class="flex items-center gap-2">, вы видите всё, что сказало бы правило, причём прямо там, где оно применяется. Между разметкой и стилями нет посредника, и искать по коду, где определён .toolbar, не приходится.

<div class="flex items-center gap-2 rounded-md border px-3 py-1.5">
  <span class="text-sm font-medium">Заказы</span>
</div>

Выделяйте компонент, а не класс

Если список классов повторяется, нужен не новый утилитарный класс, а компонент, который этим списком владеет, — например Toolbar, чья реализация и есть список. У такой абстракции появляются имя, место и причина, а изменение вносится в один файл.

export function Toolbar({ children }) {
  return (
    <div className="flex items-center gap-2 rounded-md border px-3 py-1.5">
      {children}
    </div>
  );
}

Утилиты складываются, каждая по своей оси

p-4 задаёт отступ, text-sm — размер шрифта и высоту строки, md:flex включает flex-раскладку начиная с точки останова. Во всех категориях варианты собираются одинаковым синтаксисом модификаторов, поэтому один выученный префикс — адаптивность, состояние, тёмная тема — даёт шаблон и для остальных.

<button class="rounded bg-teal-700 px-3 py-1.5 text-sm text-white hover:bg-teal-600 disabled:opacity-50">
  Сохранить
</button>

Порядок задаёт фреймворк, а не файл

Утилиты генерируются в фиксированном порядке, поэтому два класса, которые задают одно свойство, разрешаются предсказуемо и не зависят от того, где их написали. Борьба специфичностей исчезает — но вместе с ней отпадает и возможность починить конфликт, переставив правило; этот компромисс и приходится принимать.

Произвольные значения — путь отхода, а не привычка

w-[42ch] и bg-[#0f766e] существуют ради единственного случая, который токен действительно не выражает. Если произвольных значений в кодовой базе много, значит дизайн-системой перестали пользоваться и пишут встроенный CSS с лишними шагами.

Практика

Переоформите один компонент утилитами

Возьмите компонент с написанной вручную таблицей стилей, перепишите его одними утилитарными классами и удалите таблицу стилей.

Готово, когда: компонент выглядит идентично, ни одно правило из старой таблицы стилей больше не подключается, а список классов внутри компонента не повторяется.

Найдите точку выделения

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

Готово, когда: список классов существует в одном экземпляре, каждое место вызова передаёт только отличающееся, а имя компонента можно произнести вслух.

Частые ошибки

  • Повторение одного и того же длинного списка классов — следующее изменение придётся вносить четырежды, а внесут трижды; исправить: выделите компонент, который владеет списком.
  • Изобретение семантических классов внутри утилитарного фреймворка — вы вернулись к проблеме именования, от которой сбежали, но инструментов стало меньше; исправить: вместо этого выделите компонент.
  • Борьба с утилитой при помощи произвольных значений — дизайн-система обойдена стороной, согласованность потеряна; исправить: добавьте значение в тему или примите шкалу фреймворка.
  • Сборка имён классов конкатенацией строк — сканер их не видит, и в продакшене стили пропадают; исправить: пишите полное имя класса в каждом месте вызова.
  • Все утилиты в разметке и никакой абстракции — шаблоны становятся нечитаемыми и непроверяемыми; исправить: выделяйте на втором повторении, а не на десятом.

Что читать дальше

  • Каскадные таблицы стилей (CSS) (02-frontend/05-css) — каскад и специфичность — то, что утилиты намеренно обходят стороной, но понимать их всё равно нужно.
  • Конфигурация и дизайн-токены (02-frontend/14-tailwind-css/02-configuration-and-design-tokens) — откуда берутся значения в этих классах.
  • UI-компоненты (02-frontend/12-ui-components) — решение о выделении, к которому этот урок постоянно возвращает.