БЭМ + Tailwind: прагматичный гибрид для современного фронтенда
- суббота, 5 июля 2025 г. в 00:00:05
АААААААААААА!!! 15 МИНУТ МОЕЙ ЖИЗНИ ТОЛЬКО ЧТО УШЛИ НА ПРИДУМЫВАНИЕ ИМЕНИ ГРЁБАНОМУ DIV-КОНТЕЙНЕРУ!
Знакомо, да? Не я один, кто неистово бился головой о клавиатуру, пытаясь придумать, как назвать очередную обертку для обертки внутри обертки?
Я УСТАЛ. Мы все устали. Устали от:
Бесконечного .header__nav-container__menu-wrapper__item-list__element
😵💫
Спора на код-ревью о том, должен ли это быть block__element
или просто новый block
🤬
"Не используй margin на блоке! Это нарушает принципы БЭМ!" — и последующего создания еще одного контейнера 🤦♂️
Конфликтов имён классов в разных компонентах ⚔️
А потом приходит Tailwind и превращает твой HTML в нечитаемую простыню из 20 классов на элемент 📜
"Я просто хотел сверстать карточку товара, а закончил философским вопросом о природе существования блоков и элементов."
В какой-то момент я понял, что трачу больше времени на придумывание названий классов, чем на написание самого CSS. И подозреваю, что не я один такой.
Но что, если я скажу, что можно прекратить эти мучения?
В этой статье я расскажу, как я наконец-то перестал биться в конвульсиях при создании очередного div'а и нашел баланс между структурированностью БЭМа и удобством утилитарного подхода Tailwind. Больше никаких религиозных войн и догматизма — только прагматизм и эффективность.
БЭМ — отличная методология, которая решает множество проблем большого проекта. Ага, и создает тысячу новых. Но давайте будем честными, у неё есть свои недостатки:
<!-- Ад нейминга -->
<div class="content-block">
<div class="content-block__wrapper">
<div class="content-block__wrapper-inner">
<div class="content-block__wrapper-inner-container">
<!-- Наконец-то контент! Осталось только пробраться через эти девять кругов ада нейминга -->
</div>
</div>
</div>
</div>
Мучительный нейминг для вспомогательных элементов и контейнеров (Серьезно, кто придумал, что все должно иметь осмысленное имя? Моя кошка и та не страдает, когда я называю её "кис-кис-кис")
Проблема с отступами — в чистом БЭМе блок не должен иметь margin, поэтому приходится создавать дополнительные обертки (Потому что, видите ли, margin — это как НЛО: все знают, что они существуют, но никто не признается в их использовании)
Раздутая разметка из-за множества уровней вложенности (Ничто так не поднимает самооценку, как ощущение, что ты архитектор, спроектировавший Бурдж-Халифу из div'ов)
Сложность переиспользования для позиционирования — приходится создавать множество модификаторов (block_position-top_margin-left_slightly-to-the-right-no-not-that-right-a-bit-more... идеально!)
С другой стороны, чистый Tailwind часто приводит к "салянке" классов, потере семантики и усложнению чтения HTML. (Потому что ничто не говорит "я знаю, что делаю" лучше, чем 42 класса на одном элементе)
Имеешь дело с семантически очевидными компонентами: .product-card
, .user-profile
, .catalog
(Ключевое слово: "очевидными". Если ты полчаса думаешь, как назвать div, это уже не очевидно, Капитан Очевидность!)
Работаешь с компонентами с богатой внутренней структурой (Как в большой семье — каждый элемент должен знать своё место и родословную до пятого колена)
Создаешь переиспользуемые компоненты с разными вариациями (Потому что DRY расшифровывается как "Don't Repeat Yourself", а не "Damn, Repeat Yelp!")
// SCSS с использованием @apply для утилит Tailwind
.product-card {
@apply bg-white rounded-lg shadow-sm overflow-hidden;
// 👆 Смотри-ка! Одна строка вместо 10 строк CSS! Какая экономия, прямо как в рекламе стирального порошка!
&__image {
@apply w-full object-cover aspect-video;
}
&__body {
@apply p-4;
}
&__title {
@apply text-lg font-medium text-gray-900;
}
&__price {
@apply text-primary-600 font-bold mt-2;
// Ой, margin-top! Не показывайте это БЭМ-пуристам 🙈
&_discounted {
@apply line-through text-gray-400 font-normal;
}
}
// Модификатор для самого блока
&_featured {
@apply shadow-md border-2 border-primary-500;
// А здесь можно было бы создать еще один блок "featured-card",
// пять оберток и два миксина. Но мы не ищем легких путей, верно?
}
}
Что дает:
Четкую структуру компонента (Как государство, но без налогов)
Легкое переиспользование (Когда-нибудь ваш компонент вырастет и станет библиотекой, и вы будете им гордиться!)
Хорошую читаемость благодаря SCSS (Потому что нормальные люди не хотят читать CSS, где селекторы длиннее, чем названия лекарств)
Повторно используемые стили утилит благодаря @apply (Tailwind как пицца — берешь только те кусочки, которые нравятся)
Работаешь с вспомогательными контейнерами и обертками (Те самые, которым мы всегда даем имена типа wrapper, container, box, и — в момент отчаяния — thingy)
Создаешь однократные элементы без особой семантики (Потому что не всё заслуживает имени, некоторым достаточно просто быть flex-1 p-4)
Имеешь сложности с придумыванием осмысленного имени (Когда все хорошие имена уже заняты, а плохие не пропустит код-ревью)
<!-- Вместо мучений с .layout__content-wrapper__inner-container -->
<div class="max-w-screen-xl mx-auto px-4 py-8">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Содержимое, которое не нужно искать в CSS-файлах за тридевять земель -->
</div>
</div>
Что дает:
Меньше головной боли с названиями (Потому что "придумывание имен" должно относиться к детям и домашним питомцам, а не к div'ам)
Быстрая разработка (Ты вернешься домой к ужину, а не будешь до полуночи подбирать имя для очередного элемента)
Отсутствие дополнительных CSS-файлов для вспомогательных элементов (Меньше файлов = меньше мест, где можно потеряться)
Вот где гибридный подход по-настоящему сияет. ✨ Вместо создания модификаторов для каждого варианта отступа и позиционирования (я тебя умоляю, кому нужны .product-card_margin-bottom-16 .product-card_margin-bottom-32 .product-card_margin-left-0-on-mobile?!), используем Tailwind поверх БЭМ-классов:
<!-- Внутренняя структура через БЭМ, позиционирование через Tailwind -->
<article class="product-card mb-4 md:mb-0 md:mr-6 flex-1">
<img
class="product-card__image"
src="product.jpg"
alt="Product"
/>
<div class="product-card__body">
<h3 class="product-card__title">Отличный продукт</h3>
<div class="flex items-center justify-between mt-3">
<!-- Да, я использовал флексы внутри БЭМ-блока! Арестуйте меня! -->
<p class="product-card__price">1999 ₽</p>
<button class="product-card__button">Купить</button>
</div>
</div>
</article>
Обратите внимание:
product-card
и его внутренние элементы следуют БЭМ (Потому что мы воспитанные люди)
Внешние отступы (mb-4
, md:mb-0
, md:mr-6
) и некоторое позиционирование (flex-1
) задаются через Tailwind (Потому что жизнь слишком коротка, чтобы создавать модификаторы для каждого пикселя отступа)
Для уникальной внутренней структуры, которую сложно выразить в БЭМ, также используем Tailwind (flex items-center justify-between mt-3
) (Потому что иногда flex — это просто flex, а не product-card__price-button-row_layout-horizontal)
Особенно полезен гибридный подход при работе с секциями сайта, которые переиспользуются в разных контекстах (Ох уж эти секции, которые выглядят одинаково, но дизайнер говорит "здесь отступ должен быть чуть побольше, а здесь — градиент поярче"):
<!-- Базовая структура через БЭМ, специфичные стили через Tailwind -->
<section
class="hero-section bg-gradient-to-r from-blue-500 to-indigo-600 py-16 md:py-24"
>
<div class="container mx-auto px-4">
<h1 class="hero-section__title text-center md:text-left"
>Заголовок страницы</h1
>
<p class="hero-section__subtitle mb-8 max-w-2xl"
>Подробное описание сервиса</p
>
<div class="hero-section__buttons flex flex-col sm:flex-row gap-4">
<!-- Да, мы используем gap вместо margin между кнопками!
И нет, нам не стыдно! Это 2023-й, а не каменный век! -->
<button class="hero-section__button_primary">Начать</button>
<button class="hero-section__button_secondary">Подробнее</button>
</div>
</div>
</section>
.hero-section {
&__title {
@apply text-3xl md:text-5xl font-bold text-white;
// Мама, смотри! Я адаптивную типографику без медиа-запросов сделал!
}
&__subtitle {
@apply text-lg text-white/80;
}
&__button {
&_primary {
@apply bg-white text-blue-600 font-medium py-3 px-6 rounded-lg;
}
&_secondary {
@apply bg-transparent border-2 border-white text-white py-3 px-6 rounded-lg;
// 15 строк CSS в одну! Дизайнер даже не заметит подмены!
}
}
}
Такой подход позволяет переиспользовать секции, настраивая их внешний вид и позиционирование через Tailwind прямо в месте использования. (А не через 50 модификаторов, имена которых заставляют задуматься о смене профессии)
В мире компонентов гибридный подход тоже прекрасно работает (если ваш тимлид не фанатик какой-то одной методологии):
<!-- ProductCard.vue -->
<template>
<article
class="product-card"
>
<img
class="product-card__image"
:src="product.image"
:alt="product.name"
/>
<div class="product-card__body">
<h3 class="product-card__title">{{ product.name }}</h3>
<p
class="product-card__price"
:class="{ 'product-card__price_discounted': product.hasDiscount }"
>
{{ product.price }}
</p>
</div>
</article>
</template>
И используем его так:
<template>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
class="mb-4 md:mb-0"
<!-- Смотрите, никаких custom-component-margin-for-this-specific-case модификаторов! -->
/>
</div>
</template>
Микро-проекты — для совсем небольших проектов часто достаточно просто Tailwind (Чтобы потом через 6 месяцев проклинать себя, когда "маленький проект" вырастет в монстра, но кто ж об этом думает в начале?)
Строгие требования к методологии — если в команде есть строгие гайдлайны по БЭМ или Tailwind (Или если ваш тимлид носит футболку "БЭМ или смерть")
Проекты с устаревшим CSS — может быть сложно интегрировать в унаследованную кодовую базу (Особенно если там есть !important в каждой второй строке и inline-стили "потому что так быстрее")
Гибридный подход БЭМ + Tailwind это не ересь (хотя пуристы с обеих сторон могут сжечь вас на костре), а прагматичное решение реальных проблем:
Перестаем мучиться с неймингом вспомогательных элементов (Нет больше "как назвать этот div, который просто центрирует контент?")
Решаем проблему с отступами и позиционированием (Потому что отступы — это отношения между элементами, а не их внутреннее дело!)
Сохраняем семантическую структуру для сложных компонентов (Чтобы разобраться даже спустя год)
Ускоряем разработку благодаря утилитам (И успеваем закончить до дедлайна, что уже чудо)
Знаете, я ведь не просто так все это пишу. Я лично использовал этот подход на реальном коммерческом проекте, когда уже был готов наплевать на все методологии и начать писать стили прямо в атрибутах (да, было настолько х**во).
Когда мой проект вырос до 30+ компонентов, я начал замечать, что:
На придумывание имён для вложенных контейнеров уходило больше времени, чем на саму верстку
Каждый новый элемент вызывал приступ экзистенциального кризиса: "это новый бл**ь блок или элемент существующего блока?"
Меня начинало потряхивать от необходимости создавать очередной модификатор типа block__element_margin-bottom-md
И тут меня осенило. Если я использую БЭМ для чётких семантических блоков, а Tailwind — для всего остального, жизнь становится прекрасной! Разработка ускорилась примерно в два раза. Я перестал тратить время на бессмысленные споры с самим собой о том, как назвать очередной ёб**ый контейнер, который просто центрирует содержимое.
После перехода на гибридный подход:
Верстать стало удовольствием, а не пыткой
Компоненты действительно стали переиспользуемыми без тонны модификаторов
Код-ревью перестали превращаться в лекции по правильному именованию в БЭМ
Дизайнер перестал закатывать глаза, когда я говорил, что внести "небольшие изменения в отступы" займёт полдня
И самое главное — вы сэкономите кучу нервов. Серьезно, это как чёртово освобождение. Просто попробуйте эту ох**нную комбинацию, и вы поймёте, о чём я.
Не нужно быть религиозным фанатиком одной методологии — вместо этого используйте лучший инструмент для конкретной задачи. Иногда это БЭМ, иногда Tailwind, а часто — их разумное сочетание. Я за**ался тратить время на споры о методологиях, когда можно просто делать крутой продукт!