39 Модуль 8 Архитектура стилей 16 мин урок 14 из 25

Компонентный CSS против утилитарного

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

Что ты научишься делать

  • Соберёшь один блок двумя способами и увидишь разницу в числах
  • Поймёшь, какую задачу на самом деле решает Tailwind
  • Выберешь гибрид, который работает в большинстве проектов

#Два ответа на один вопрос

Вопрос простой: где живёт оформление — в CSS или в разметке.

Компонентный подход: в разметке имя сущности, в CSS её описание. class="card" — а как выглядит карточка, написано в стилях.

Утилитарный подход: в разметке набор атомарных классов, каждый отвечает за одно свойство. class="p-4 bg-white rounded-xl" — и никакого CSS писать не нужно.

Интерактивный разбор доступен при включённом JavaScript.

#Что реально решает утилитарный подход

Tailwind популярен не потому, что «модно». Он закрывает четыре настоящие боли, знакомые каждому, кто вёл проект дольше полугода.

БольКак её решают утилиты
CSS растёт и никогда не уменьшаетсяНовый блок не добавляет ни строки стилей
Страшно удалять правила: непонятно, где используютсяУдаляешь разметку — оформление уходит вместе с ней
Час на придумывание имени для обёрткиИмена не нужны вообще
Значения расползаются: то 13px, то 14pxДоступны только шаги шкалы

#Чем за это платят

  • Разметка перестаёт читаться. Двенадцать классов на элементе прячут структуру. Чтобы понять, что это карточка товара, приходится вглядываться.
  • Повторение. Двадцать карточек — двадцать одинаковых наборов классов. Поменять все разом уже нельзя: нужно править каждую или заводить компонент.
  • Нужна сборка. Полный набор утилит весит много; в проект попадают только использованные — а значит, требуется инструмент, который их вычислит.
  • Свой язык. rounded-xl и gap-2 надо знать. Обычный CSS знают все.

Так не надо

<!-- Что это за блок? Придётся вчитываться -->
<div class="flex items-center gap-3 p-4 rounded-xl
  bg-white shadow-sm hover:shadow-md transition
  border border-gray-200 dark:bg-gray-800">

Оформление видно сразу, а смысл — нет. И так на каждом элементе страницы.

Так надо

<!-- Сразу понятно, что это -->
<article class="product-card">

Разметка описывает содержание. Оформление лежит там, где ему место, — в стилях.

#Гибрид, который обычно и получается

На практике команды редко выбирают крайность. Работающее правило деления такое.

Компоненты — для сущностей. Всё, у чего есть название в предметной области: карточка товара, шапка, форма заказа.

Утилиты — для раскладки и мелких правок. Отступ между блоками, выравнивание, скрытие на мобильном. То, что не является частью сущности и меняется от места к месту.

HTMLКак это выглядит
<!-- Утилиты снаружи: расстояния задаёт контейнер --><section class="stack gap-6">  <!-- Компонент внутри: сущность со своим именем -->  <article class="product-card">    <h3 class="product-card__title">Раф</h3>    <p class="product-card__price">240 ₽</p>  </article>  <!-- Утилита для разовой правки, без нового класса -->  <p class="text-center">Показать ещё</p></section>

#Утилиты своими руками

Чтобы получить пользу от утилит, фреймворк не обязателен. Двадцать классов покрывают девяносто процентов случаев, и их можно написать руками за полчаса.

CSSutilities.css
@layer utilities {  /* Раскладка */  .stack { display: grid; }  .row { display: flex; align-items: center; }  .row--between { justify-content: space-between; }  /* Расстояния — по шкале, произвольных чисел нет */  .gap-1 { gap: var(--space-1); }  .gap-2 { gap: var(--space-2); }  .gap-4 { gap: var(--space-4); }  .gap-6 { gap: var(--space-6); }  /* Текст */  .text-center { text-align: center; }  .text-muted { color: var(--color-muted); }  .text-sm { font-size: .875rem; }  /* Состояния видимости */  .is-hidden { display: none; }  .visually-hidden {    position: absolute;    inline-size: 1px;    block-size: 1px;    overflow: hidden;    clip-path: inset(50%);  }}

#Что происходит с файлом за год

Разница между подходами лучше всего видна не на одной карточке, а на графике роста.

Компонентный CSS растёт примерно линейно: каждый новый блок добавляет свои правила. Утилитарный выходит на плато: после первой сотни классов новые почти не появляются, потому что набор уже покрывает нужды.

Через сколькоКомпонентыУтилиты
Месяц~600 строк CSS, разметка читается~400 строк набора, разметка шумная
Полгода~2500 строк, часть уже не используетсятот же набор, разметка так же шумная
Год~5000 строк, десятая часть мёртваянабор почти не вырос
Удалили разделправила остались, тронуть боятсяоформление ушло вместе с разметкой

#Как это выглядит в реальном проекте

CSSПорядок слоёв решает спор
@layer reset, base, layout, components, utilities;@layer components {  .product-card {    display: grid;    gap: var(--space-2);    padding: var(--space-4);    background: var(--color-surface);    border-radius: var(--radius-md);  }}@layer utilities {  /* Побеждает компонент по определению, без !important */  .p-0 { padding: 0; }  .text-center { text-align: center; }}
Утилита перекрывает компонент
В песочницу

#Когда НЕ надо

  • Утилиты без сборки на большом сайте. Полный набор весит сотни килобайт. Без инструмента, вырезающего неиспользуемое, это чистый вред.
  • Компоненты для одноразового блока. Если блок встречается один раз и больше не появится, класс с именем — лишняя сущность. Хватит утилит.
  • Смешивать без правила. Хуже обоих подходов — когда часть оформления в компоненте, часть в утилитах, и никто не знает, где искать. Договорённость важнее выбора.
  • Спорить об этом дольше часа. Оба подхода рабочие. Проект гораздо чаще умирает от отсутствия договорённости, чем от неправильного выбора.

Где спотыкаются почти все

Утилиты с !important на каждом правиле

Почему Так делали до слоёв каскада. Теперь важность мешает: перекрыть утилиту в исключительном случае становится нечем.

Как надо Положи утилиты в последний слой @layer utilities — они победят и без важности.

Компонент со ста утилитами внутри

Почему Разметка перестаёт читаться, а изменить все карточки разом уже невозможно.

Как надо Повторяется больше двух-трёх раз — заводи компонент.

Утилиты с произвольными значениями

Почему Классы вроде .mt-13 убивают главную пользу шкалы: значения снова расползаются.

Как надо Только шаги шкалы. Нужно другое значение — это повод для компонента.

Дублирование: класс компонента и те же утилиты рядом

Почему Два источника правды. При правке компонента результат не меняется, потому что утилита сильнее.

Как надо Реши, кто отвечает за это свойство, и убери второе.

Компонентный CSS component CSS
Подход, где класс называет сущность, а оформление живёт в стилях.
Утилитарный CSS utility-first CSS
Подход, где каждый класс отвечает за одно свойство, а оформление живёт в разметке.
Атомарный класс atomic class
Класс из одного объявления: .text-center, .gap-2.
Мёртвый CSS dead CSS
Правила, которые больше нигде не применяются, но их боятся удалить.

Проверь себя

  1. Какую боль утилитарный подход решает лучше всего?

    Разметку удалить не страшно. Правила в компонентном CSS остаются, и со временем никто не решается их трогать.

  2. Почему в старых наборах утилит везде стоит !important?

    Со слоями важность больше не нужна: достаточно положить утилиты в последний слой.

  3. Где проходит граница в гибридном подходе?

    Вопрос не в размере, а в принадлежности: скругление — часть карточки, расстояние между карточками — нет.

  4. Что плохого в утилите <code>.mt-13</code>?

    Смысл шкалы в ограничении выбора. Как только появляются произвольные значения, возвращается прежний хаос.

  5. Что хуже обоих подходов?

    Оба подхода рабочие. Проблемы начинаются, когда правила деления нет и каждый решает по-своему.

Задание

Собрать блок двумя способами

Слева карточка на утилитах, справа — место под компонентную версию.

Сделай две вещи: заведи слои @layer components, utilities; собери класс .card со всем оформлением и повесь его на вторую карточку вместо утилит. Утилиту .text-center оставь работающей — она должна перекрывать компонент без !important.

Открыть в песочнице
    Подсказка 1

    Первой строкой: @layer components, utilities;

    Подсказка 2

    Компонент: @layer components { .card { display: grid; gap: 8px; padding: 16px; background: #f4f4f8; border-radius: 12px; text-align: left; } }

    Подсказка 3

    Все существующие утилиты заверни в @layer utilities { … } — тогда .text-center победит без важности.

    Показать решение
    HTMLindex.html
    <article class="p-4 bg-surface rounded-md gap-2 grid">  <h3 class="m-0 text-md">На утилитах</h3>  <p class="m-0 text-sm text-muted">Оформление в разметке</p></article><article class="card text-center">  <h3>Компонентная</h3>  <p>Здесь должен быть класс card</p></article>
    CSSstyle.css
    @layer components, utilities;@layer components {  .card {    display: grid;    gap: 8px;    margin-top: 12px;    padding: 16px;    background: #f4f4f8;    border-radius: 12px;    text-align: left;  }  .card h3 { margin: 0; font-size: 16px; }  .card p { margin: 0; font-size: 14px; color: #5b6070; }}@layer utilities {  .p-4 { padding: 16px; }  .bg-surface { background: #f4f4f8; }  .rounded-md { border-radius: 12px; }  .gap-2 { gap: 8px; }  .grid { display: grid; }  .m-0 { margin: 0; }  .text-md { font-size: 16px; }  .text-sm { font-size: 14px; }  .text-muted { color: #5b6070; }  .text-center { text-align: center; }}

    Проверь себя