Второй месяц: стили, которые не гниют
CSS лёгкий три недели и невыносим к третьему месяцу. Не потому, что его плохо пишут, — потому, что в языке ничто не мешает файлу стилей стать местом, где удаление превращается в лотерею.
Первая неделя: пара сотен строк, всё очевидно.
Третий месяц: четыре тысячи строк. Удалять никто не решается, потому что узнать, на что влияет правило, невозможно. Каждое новое изменение требует на один селектор больше предыдущего, и половина заканчивается на !important. Где-то внутри живут четырнадцать оттенков серого, девять значений отступа между 12 и 20 пикселями и три определения «карточки».
Ничего небрежного не произошло. Каждое отдельное изменение было разумным. Проблема структурная, и у неё две причины, которые стоит назвать точно:
CSS глобален. Любое написанное правило применяется ко всему документу. Класс, написанный в одном файле, оформит подходящий элемент в файле, который ты никогда не открывал.
Удаление небезопасно. Ничто не сообщает, используется ли правило до сих пор. Поэтому никто не удаляет, все добавляют, и файл только растёт.
CSS гниёт не оттого, что его плохо пишут. Он гниёт оттого, что ничто этому не мешает: каждое правило глобально, а каждое удаление — ставка.
Всё дальнейшее — способы навязать снаружи те две вещи, которых язык не даёт: область видимости и согласованность.
Токены: решить один раз, пользоваться везде
Четырнадцать оттенков серого — видимый симптом. Причина в том, что каждый цвет выбирался в момент, когда понадобился, тем, кому понадобился, и не было ответа на вопрос «какой серый — наш серый».
Кастомные свойства чинят это, превращая решение в одно и давая ему имя. Небольшой набор значений, объявленный в корне, на который ссылаются везде: цвета, шкала отступов, шкала размеров текста, скругления, тени, длительности переходов.
Правило, благодаря которому это работает, строже, чем ожидают: внутри компонента никаких сырых значений. Ни шестнадцатеричного кода, ни числа пикселей вне шкалы. Если нужного значения не существует, это решение, которое принимается в файле токенов — осознанно, один раз, — а не число, которое печатают на месте.
Основную нагрузку несут две шкалы:
Отступы. Почти геометрическая последовательность — 4, 8, 12, 16, 24, 32, 48, 64 — и ничего между. Конкретные числа важны куда меньше того факта, что их восемь, а не сорок. Интерфейсы выглядят «как-то не так» по причине, которую трудно увидеть и легко устранить: промежуток в 13 пикселей рядом с 16 и 18 читается как неряшливость даже тем, кто не может это назвать.
Текст. Пять-шесть размеров с подобранными межстрочными интервалами, и любой текст на сайте использует один из них.
Здесь же ровно та точка, где встречаются дизайн и код. Статья про дизайн-системы в сфере «UX-дизайн» описывает тот же объект с другой стороны: токены и есть контракт между двумя дисциплинами. Когда шкала отступов у дизайнера и переменные в твоём CSS — одни и те же восемь чисел, целый класс споров исчезает.
Область видимости: три честных ответа
У проблемы глобальности есть три реально применяемых решения. Работают все три. Не работает — не иметь ни одного или иметь все три в разных папках.
Соглашение об именах — BEM и родственники. Каждому классу даётся имя, кодирующее компонент и роль, поэтому card__title и card--featured не сталкиваются ни с чем по построению. Не требует инструментов и работает где угодно — поэтому до сих пор встречается повсюду. Цена — многословность и дисциплина: соблюдать это некому, кроме тебя.
Изолированные стили — CSS Modules, однофайловые компоненты, scoped-атрибуты. Сборщик переписывает твои имена классов в уникальные для файла, и столкновения становятся невозможными, а не просто маловероятными. Модель в голове остаётся обычным CSS. Сейчас это умолчание в большинстве компонентных фреймворков, и если ты пользуешься одним из них, рекомендовать проще всего именно это.
Утилитарные классы — Tailwind и подобные. Ты перестаёшь писать файлы стилей и собираешь вид из фиксированного словаря крошечных классов прямо в разметке. Область видимости перестаёт быть проблемой, потому что ничего не именуется; согласованность обеспечена, потому что утилиты и есть шкала. Издержки реальны, и их стоит назвать: разметка становится шумной, а повторяющиеся куски приходится выносить в компоненты, иначе ты будешь вечно копировать по пятнадцать классов руками. Это настоящий размен, а не мода, — и вырос он так быстро потому, что делает обе трудные проблемы этой статьи структурно невозможными.
Выбирай одно на проект. Неподдерживаемая кодовая база почти всегда та, где их три.
Компонент оформляет себя
Что бы ты ни выбрал, одно правило делает для поддерживаемости больше, чем все остальные вместе: компонент оформляет себя и ничего больше.
Конкретно: ни один селектор внутри компонента не дотягивается наружу, чтобы поменять что-то в другом компоненте. Никаких .sidebar .button, переопределяющих вид кнопки снаружи. Если кнопка в сайдбаре должна выглядеть иначе — это вариант кнопки, решаемый кнопкой и выставленный наружу классом или пропом.
Раскладка — исключение, подтверждающее правило, и у неё есть чистое решение: компонент решает своё внутреннее устройство, а родитель решает, где компонент стоит и сколько места вокруг. Поэтому внешние отступы принадлежат раскладке, а не компоненту: карточка со встроенным нижним отступом окажется неправа в первом же контексте, которому этот отступ не нужен.
Держи вложенность мелкой — не больше двух-трёх уровней. Нативная вложенность в CSS теперь есть и она приятна, но это та же ловушка специфичности в более симпатичной одежде: пять уровней дают селектор, который уже ничем не перебить вежливо.
Слои, в одном порядке
Файл стилей остаётся понятным, когда у него есть известный всем порядок:
- Сброс и база — нормализация умолчаний,
box-sizing, базовая типографика, стили фокуса. - Токены — переменные.
- Раскладки — каркасы страниц и переиспользуемые схемы расстановки.
- Компоненты — основной объём.
- Утилиты — небольшой набор однозадачных помощников, если они у тебя есть.
В современном проекте @layer делает этот порядок явным и обязательным: более поздний слой выигрывает независимо от специфичности, поэтому компонент переопределяет базовый стиль без драки. Одна эта возможность убирает бо́льшую часть причин, по которым вообще набирают !important.
Тест на удаление
Вот проверка, которую стоит провести на любой кодовой базе, включая твою: выбери класс, который считаешь неиспользуемым, и удали его. Можешь ли ты меньше чем за минуту убедиться, что ничего не сломалось?
Если да — архитектура работает. Если приходится грепать весь проект, прокликивать шесть страниц и всё равно остаётся тревога, ты нашёл настоящую болезнь, — а лечение ровно то, что описано выше: изоляция, чтобы у имени был один дом, и соседство, чтобы стили жили рядом с использующей их разметкой.
Это же и честная мера того, понимаешь ли ты собственные стили, и тест этот противнее, чем кажется. Код, который работает сегодня, ощущается понятым; сфера «Как учиться» назвала бы это хрестоматийной иллюзией компетентности — беглость, принятая за знание. Настоящая проверка не в том, чтобы это написать. Она в том, чтобы это изменить через три месяца, когда ощущение понимания испарилось и осталась одна структура.
На практике
Напиши токены до первого компонента. Двадцать минут вначале: цвета, восемь значений отступа, шесть размеров текста, два скругления. Всё дальнейшее — выбор из меню вместо решения с нуля.
Запрети себе сырые значения в стилях компонентов на один проект. Каждый раз, когда хочется произвольного числа, иди и посмотри, нет ли его в шкале. Почти всегда есть, и привычка ставится за неделю.
Посчитай свои оттенки серого. Поищи в CSS шестнадцатеричные коды и выпиши уникальные. Число обычно шокирует, и это самая быстрая демонстрация того, зачем нужны токены.
Прочитай собственные стили трёхмесячной давности. Не чтобы починить — чтобы заметить, какие места ты не можешь восстановить. Эти места и есть обратная связь: они показывают, чего твои имена на самом деле не сказали.
Проводи тест на удаление раз в месяц. Двухминутная проверка здоровья того, от чего зависит, будут ли приятными следующие полгода.
Выбери одно соглашение и запиши его в readme проекта, в три строки. Половина гниения CSS — это два человека, каждый из которых следует своей разумной системе.
Проверь себя
Закрой статью и ответь своими словами:
- Какие два структурных свойства CSS приводят к гниению стилей?
- Что даёт токен помимо экономии нескольких нажатий?
- Почему шкала из восьми значений отступа выглядит лучше свободного выбора?
- Назови три подхода к изоляции и по одной честной издержке каждого.
- Почему внешние отступы должны принадлежать раскладке, а не компоненту?
- Что
@layerменяет в переопределении и что он заменяет? - Что такое тест на удаление и о чём говорит его провал?
Коротко
- CSS гниёт по двум структурным причинам: любое правило глобально, и ничто не сообщает, нужно ли правило до сих пор.
- Токены превращают каждое визуальное решение в одно и дают ему имя; внутри компонентов — никаких сырых значений.
- Шкала отступов и шкала текста несут бо́льшую часть визуальной согласованности и заодно являются контрактом с дизайном.
- Есть три работающих ответа на изоляцию — соглашение об именах, изолированные стили, утилитарные классы. Выбирай одно на проект; каша — это когда их три.
- Компонент оформляет себя и ничего больше; родитель решает положение и расстояние вокруг.
- Держи вложенность в двух-трёх уровнях: нативная вложенность — та же ловушка специфичности в другой одежде.
- Упорядочь стили как сброс, токены, раскладки, компоненты, утилиты — и пусть порядок обеспечивает
@layer, а не!important. - Тест на удаление измеряет архитектуру, а перечитывание собственного старого CSS измеряет, понял ты его или просто написал.