Я долго был скептически настроен к Tailwind.
Казалось, что utility-классы только усложняют JSX: вместо аккуратного CSS или SCSS-модуля получаешь длинный className, в котором приходится разбираться прямо во время чтения кода. А ещё я слышал от коллег и сам подозревал, что с utility-классами тяжелее дебажить — вместо одного осмысленного имени компонента в DevTools видишь десятки разрозненных классов.
Я привык к CSS-модулям, BEM и разделению разметки и стилей. Поэтому долго не видел особой причины переходить на Tailwind.
А потом решил просто попробовать.
И в итоге пришёл к довольно простому выводу: Tailwind — не замена всему CSS, но для малых и средних проектов это действительно удобный инструмент. При этом часть моих исходных опасений подтвердилась — просто не оказалась критичной.
Почему я всё-таки дал ему шанс
Не сказать, что все сомнения ушли — но несколько вещей действительно зашли.
Первое, что понравилось — utility-классы создают общий язык для UI.
Гриды, flex, отступы, цвета и размеры больше не требуют создания собственных классов вроде .flex-center, .mt-lg или .u-cols-3.
Вместо этого:
className = "grid grid-cols-3 gap-6";
И неважно, кто писал этот код — большинство разработчиков сразу понимают, что он делает.
Второе — контекст находится рядом с разметкой.
Открываешь .tsx и сразу видишь и структуру, и стили. Не нужно переключаться между файлом и styles.module.css, чтобы понять, как именно выглядит элемент.
Для работы с AI это тоже оказалось довольно удобным. Модели не приходится дополнительно искать связанные CSS-файлы, чтобы понять стили интерфейса.
Третье — готовые решения проще переносить между проектами.
Многие современные библиотеки и шаблоны используют Tailwind. Если нужно быстро собрать, например, карточку или форму, часто достаточно взять существующую структуру и адаптировать её под проект.
Что я собрал
В качестве эксперимента я собрал frontend SPA для todo-приложения.
Стек:
- React 19 + TypeScript + Vite
- React Router с lazy loading страниц
- Redux Toolkit + RTK Query
- MSW + локальный mock API
- Tailwind CSS v4
- class-variance-authority + tailwind-merge
- GSAP для возможных анимаций
Архитектура разделена по слоям:
app
pages
widgets
features
entities
shared
Это не идеальная реализация FSD, но структура уже позволяет нормально разделять ответственность между частями приложения.
Где Tailwind реально помог
Быстрая сборка интерфейса.
Особенно хорошо Tailwind показал себя в layout, адаптиве и небольших UI-элементах.
Стили рядом с разметкой.
Для простых UI-блоков это действительно удобно: не нужно создавать отдельный файл стилей ради нескольких свойств.
Варианты UI-элементов.
В связке с cva получилось удобно описывать разные состояния и варианты UI-элементов:
ButtonTaskColumnTabs
Например, вместо большого количества условных классов можно централизовать варианты через cva.
Dark mode.
dark: оказался очень удобным способом описывать тёмную тему непосредственно рядом с соответствующим стилем.
Быстрая разработка.
Auth-страницу удалось собрать довольно быстро, при этом интерфейс уже выглядит достаточно цельно.
Где появились проблемы
Главный недостаток я заметил на крупных JSX-блоках.
Например:
src/pages/auth/ui/Page.tsxsrc/widgets/sidebar/ui/Sidebar.tsxsrc/App.tsx
Когда className начинает содержать десятки utility-классов, структура JSX становится менее читаемой.
Особенно это заметно, когда появляются многочисленные состояния и responsive-варианты:
className = "... md:... lg:... hover:... focus:...";
В какой-то момент приходится сначала разбирать классы, а уже потом понимать структуру самого блока.
Дебажить стало сложнее.
Это оказалось той самой проблемой, которой я и опасался с самого начала. Когда стилей нет в отдельном файле, а есть только россыпь utility-классов в className, в DevTools элемент отображается с десятками классов вместо одного осмысленного имени — и не сразу понятно, какой из них реально отвечает за баг.
Раньше при багфиксе достаточно было открыть Button.module.css и посмотреть все стили компонента в одном месте. Здесь приходится либо искать нужный класс визуально среди остальных, либо временно закомментировать часть className, чтобы понять, что на что влияет.
Особенно это чувствуется в связке с cva — когда варианты компонента собираются динамически, итоговый набор классов в браузере уже не совпадает один в один с тем, что написано в коде, и приходится держать в голове, как cva их комбинирует.
Появляется и другая проблема — непоследовательность абстракций.
Часть UI-элементов уже хорошо оформлена через cva, а часть всё ещё содержит длинные строки классов непосредственно в JSX.
В результате проект постепенно начинает смешивать несколько подходов к организации UI.
Итог
После этого проекта моё отношение к Tailwind стало более взвешенным, но не безоговорочным.
Он действительно ускоряет разработку интерфейсов, особенно когда речь идёт о небольших и средних проектах.
Но использовать его абсолютно везде тоже не стоит.
Сейчас я бы придерживался примерно такого подхода:
- Простые стили и layout — utility-классы.
- Варианты UI-элементов —
cva. - Сложные визуальные блоки — выносить отдельно.
- Если
classNameстановится слишком большим, стоит пересмотреть структуру блока. - Не бояться обычного CSS там, где utility-классы перестают улучшать читаемость.
Дебажить utility-классы я так и не полюбил — привычка смотреть на стили компонента одним файлом никуда не делась. Но для layout и простых состояний Tailwind реально экономит время, и ради этого я готов мириться с неудобствами в DevTools.
Tailwind для меня в итоге оказался не заменой CSS, а ещё одним инструментом.
И, пожалуй, это наиболее адекватный способ к нему относиться.
