Привет, Хабр! Меня зовут Александр Водолазских. Я живу в Новосибирске и я работаю Frontend Domain Lead в СберМаркете. Сегодня хочу немного поговорить об опыте работы с Tailwind CSS — utility-first CSS framework. Поделюсь болью и радостью, которые возникли при его эксплуатации.

Библиотека Tailwind довольно молодая, первые бета-версии появились в 2017 году. Но по количеству звёзд и форков можно понять, что комьюнити вокруг сложилось солидное. Инструмент предоставляет нам большой набор готовых классов и утилит, которые облегчают и ускоряют стилизацию приложения/сайта. Вы просто пишете короткие имена заранее спроектированных классов: цвета, отступы, позиционирование. Хватает классов и для сайзинга, типографики, анимации бэкграундов и многого другого.

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

Радует и совместимость: всё спокойно собирается и под Next.js, и под Vite, и под Angular, и под Create React App. Более того, есть возможность сборки с помощью PostCSS-плагина, что делает инструмент поистине хорошо имплементируемым в любой системе.

Но есть у Tailwind и недостатки. На самом деле это довольно холиварный инструмент.

Допустим, у вас стартап...

Представим, вы с друзьями решили сделать стартап: арендовали гараж для работы, собрали лучшую команду и подобрали крутой технологический стек. Что касается CSS — вы решили найти модное решение для стилизации, и глаз упал на Tailwind. Всем нравится, коллеги довольны, и ничто не предвещает беды.

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

Почему масштабировать и поддерживать модное решение для стилизации оказывается грустно:

  • Фреймворк неинформативен. Понятных названий классов в нём просто нет, а как навигироваться в DOM-дереве при дебаге — вообще не понятно.

  • Классов очень много, их попросту сложно запомнить, особенно вновь прибывающим молодым разрабам. Мы же растём, помните?

  • Появляются многострочные портянки из классов.

  • Неясно, каким образом привести груду написанных стилей в общую систему и избавиться от тотального бардака.

Болей достаточно много, как говорится: «Есть от чего впасть в отчаяние». Хорошая новость состоит в том, что все проблемы фреймворка можно сжать в одну: нужно чётко понимать, как его готовить. Если изначально выстроить аккуратный подход, то возможные боли при масштабировании практически нивелируются. Иными словами, у нашей команды есть тактика, и мы должны её придерживаться.

Пройдёмся по каждому из минусов отдельно.

Tailwind — неинформативное решение

Представим, что мне нужно что-то продебажить в UI. Например, проблема в карточке ритейлера. Я часто начинаю дебаг с того, что открываю DevTools и ищу нужный мне компонент по названию класса.

Вот пример нехитрой навигации в проекте, стилизованном с SCSS. Всё весьма просто и прозрачно.

Tailwind отберёт у вас понятные, читаемые названия классов. Вместо чего-то семантичного вы получите sticky top-0 z-40 w-full и так далее.

Как с этим справиться? Можно просто добавить дата-атрибуты. Да, вы никуда не денетесь от портянки классов. Как «упаковать» их красиво — так и не придумали. Да, останутся непонятные длинные имена, но вы сможете навигироваться по DOM-дереву, сможете искать. Тестировщики скажут вам спасибо, потому что дата-атрибуты очень полезны для автоматизации тестирования.

Ещё одно простое и очевидное решение — красиво разделить код на компоненты. Если у вас React, используем его DevTools, и вот искать что-то по дереву компонентов снова просто и приятно. Если не React — расстраиваемся и переходим на React. Это, конечно, шутка, все инструменты для своих задач.

В Tailwind слишком много классов, и запоминать их трудно

Иногда я слышу что-то в таком стиле: «Инструмент классно подходит для прототипов. Правда, мы с ним раньше не работали, но хотим что-то накидать на коленке». Это не очень хорошая история, на коленке не получится. При старте с нуля много времени уйдёт на то, чтобы понять, какие вообще есть классы, как правильно готовить фреймворк, чтобы работа выстроилась более-менее системно. 

Логика здесь простая: чем больше вы пишете, тем лучше запоминается. Набор классов всё же ограничен. Как всегда, тренировка и потраченные часы рабочего времени сделают своё дело.

Помимо этого, для решения проблемы есть волшебное расширение IntelliSense. Вы ставите его в VS Code или любую популярную IDE и получаете удобный Tailwind-саджестор: расширение подсказывает вам возможные варианты. Поначалу приходится часто смотреть документацию и пользоваться дополнительными средствами, чтобы ориентироваться в коде. Но, когда вы напишете 10-20 компонентов, всё дойдёт до состояния полного автоматизма, и проблема отпадёт.

Неминуемо собирается портянка из классов

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

Код становится достаточно читаемым, если использовать библиотеку classNames или что-то подобное.

Мы работаем с ней так: бьём длинный класс на небольшие подклассы, которые семантически объединяем по экранам. Можно дробить классы по другим признакам, например — в зависимости от значений пропсов. Декомпозиция здесь — наше всё. И всегда стоит помнить про компонентный подход: чем аккуратнее и точнее мы дробим, тем меньше громоздких классов у нас будет в будущем.

Однако важно сказать, что по волшебству всё это не происходит. Держите в голове командные договорённости либо ужесточайте правила ESLint или другого линтера. Иначе будет сложно контролировать, чтобы люди писали код правильно. Плагин eslint-plugin-tailwindcss даёт набор правил, который можно использовать для поддержки кода. Он отсортирует и упорядочит классы, запретит использовать неконсистентные значения.

Стилизация в Tailwind непереиспользуемая, плохо масштабируемая и бессистемная

Про переиспользование. В Tailwind есть очень полезная директива @apply, которая добавляет ваши кастомные классы на слой компонентов и позволяет использовать их везде. Это значит, что вы можете написать отдельный класс и использовать в нём набор других классов.

@layer components {
  .btn-primary {
    @apply py-2 px-4 bg-blue-500 text-white
         font-semibold rounded-lg shadow-md 
         hover:bg-blue-700 focus:outline-none 
         focus:ring-2 focus:ring-blue-400 
         focus:ring-opacity-75;
  }
}

А теперь вы можете расширить директивой класс любого элемента. Достаточно удобно!

Использование таких apply-классов даёт нам возможность экспортировать целые наборы стилей. Грубо говоря, я могу написать свои компоненты, вынести все стили в общую core-библиотеку и переиспользовать в других своих проектах!

Про масштабируемость. Фреймворк предоставляет пользователю очень гибкий конфиг. Есть tailwind.config.js, который создаётся в корне вашего проекта, когда вы инитите Tailwind. Он даёт возможность настроить всё так, как вам необходимо. С помощью этого конфига вы можете строго ограничить команду разработчиков, дав им доступ лишь к типографике, цветам, отступам, которые предусмотрены именно вашей дизайн-системой. На скрине описан вид поведения boxShadow и цветов.

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

Однако в Tailwind есть проблемы, с которыми действительно тяжело.

Легко получить неконтролируемую свалку классов

Например, у нас есть такой класс:

Разраб использовал всё, что только можно, скомбинировав:

  • Кривой и несемантичный дата-атрибут.

  • Отступы в пикселях, в отрицательных значениях и в rem.

  • Цвета в текстовых значениях и через hex.

Получилось очень мусорно и не очень читаемо. Вы скажете, что кейс неорганический, а я скажу вам, что в коде стартапов встречается и не такое ???? Поэтому важно договариваться либо ужесточать ESLint. Выше я уже писал об этом, но это так важно, что позволю себе повториться.

Другого решения нет, только так.

Большинство кастомных решений в Tailwind неудобные и сложные

Прежде чем считать это проблемой, задайте себе вопрос, часто ли вам нужно изобретать велосипед. Библиотека классов огромная, и найти что-то можно практически под любые нужды.

Но есть ситуации, когда персональный велосипед вам точно нужен. Например, Tailwind не умеет в clip-path. Инструмент развивается, у него выходят новые версии, но да, в данный момент действительно есть ряд юзкейсов, которые остались без имплементации.

Вы всегда можете написать нативный CSS и закрепить его на утилитах через директиву @layer. Вроде ничего сложного, но давайте представим, что вы каждый раз пишете под любое более-менее кастомное решение такой кусочек. Страшно подумать, во сколько раз вырастут затраты времени и сил. Если стилизация проекта слишком кастомная, лучше подумайте десять раз, стоит ли использовать Tailwind.

Из коробки нет RTL

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

Отличное комьюнити фреймворка решило эту проблему библиотекой tailwindcss-rtl. Она даёт дополнительный набор классов, с которыми ваш RTL заведётся без какого-либо труда. Недаром говорят: «Добавьте к любому слову .js — и вы получите какую-то существующую NPM-библиотеку». Здесь комьюнити действительно решает!

Аксессибилити хромает

Допустим, у нас есть div, который должен быть прочитан только скринридером и не должен отображаться в UI. Для этого нам дают класс sr-only, который абсолютно позиционирует элемент, скрывает его и применяет ряд дополнительных стилизаций. На этом всё — больше из коробки ничего в Tailwind просто нет.

Вселяет надежду находящийся на этапе тестирования пакет Tailwind CSS Beta 2 с плюшками аксессибилити вроде атрибутов aria-* разных классов. Они позволяют определять совместимость браузера с тем или иным CSS-решением. Есть и много других полезных фич.

https://github.com/tailwindlabs/tailwindcss/discussions/9574

Раз уж мы заговорили про NPM-пакеты, мне будет грустно, если я не скажу, что в связке с React вы можете писать кроссплатформенные стили, используя что-то из этой пары классных пакетов: один, два. UI будет переиспользуемым: мы можем собрать React Native под Web, используя что-то вроде react-native-web, и вкупе со стилизацией на Tailwind получить мощнейшее комбо.

Комьюнити Tailwind даёт нам ещё и готовые UI-киты (например, tailwind-kit). Вы берёте готовый код, копипастите его в свой проект, стилизованный с Tailwind, и всё. Всё работает, никаких дополнительных библиотек не нужно.

Как любой инструмент, Tailwind имеет свои трейдоффы, но он быстрый и кроссплатформенный. Когда вы выйдете на пик кривой обучения, потратив N часов и написав M компонентов, всё станет интуитивно понятно, работа по стилизации пойдёт в удовольствие.

Масштабируем ли код, написанный с Tailwind? Да, но его нужно уметь готовить. Если вы не будете договариваться либо не будете использовать свои жёсткие правила для ESLint, у команды ничего не получится.

Tailwind даёт классное комьюнити: если использовать его силу, можно реализовать практически любую идею. Для меня это решение оказалось удобным и хорошо масштабируемым в рамках проекта. Остальные выводы каждый волен делать самостоятельно. Использовать или нет — решать вам.

Tech-команда СберМаркета ведет соцсети с новостями и анонсами. Если хочешь узнать, что под капотом высоконагруженного e-commerce, следи за нами в Telegram и на  YouTube. А также слушай подкаст «Для tech и этих» от наших it-менеджеров.

Комментарии (11)


  1. Lazytech
    25.05.2023 12:47
    +5

    Упомяну еще один недостаток Tailwind CSS. Если дизайн-макет подготовлен в Figma, то весь CSS-код фактически придется "конвертировать" в формат Tailwind - по сути, делать лишнюю работу. Для сравнения, при использовании чистого CSS или CSS-фреймворков наподобие Sass/SCSS можно взять почти весь CSS-код из дизайн-макета как есть, с минимальными правками.


  1. NickyX3
    25.05.2023 12:47
    +1

    накидывание через @apply в source twcss не отменяет там исполmзование обычных css свойств. Можно спокойно понаписать что-то типа.

    .class {
      border:1px solid black;
      ...
      @apply p-4 m-0 bg-grey-500
    }

    Просто не надо тащить tw классы в html от слова совсем и будет прямо гораздо проще.


    1. weart
      25.05.2023 12:47
      +3

      Не будет


      1. NickyX3
        25.05.2023 12:47
        -2

        У нас нет tw классов в шаблонах от слова совсем, есть только конфиг + source css с apply + шаблоны. Оно еще и гит хуком в post-receive потом собирается. Мы лично счастливы. Но мы не фронтэндеры.


  1. timonbandit
    25.05.2023 12:47
    +4

    Как по мне, самый большой недостаток Tailwind - это его популярность.
    Выбор инструмента по популярности, а не по тому подходит он или нет - вот это проблема.
    Если у вас очередная админка или блог - ок.
    Но когда у тебя в фигме 20 задизайнеренных интерфейсов и каждый уникален - и еще и tailwind туда - получается помойка из класов что нужны + куча классов из tailwind. Неподдерживаемая куча.


    1. NickyX3
      25.05.2023 12:47
      +1

      Компиляция из источника в tw подразумевает что в результате будут только те css, которые есть в ваших исходниках и не будет никакой помойки. Будет исходник с@apply классов tw/возможно смешанный с нативным css + ваши html/js/whatever "шаблоны", а на выходе кроссбраузерный css только с теми классами, которые реально есть в ваших исходниках и используются


  1. Ilusha
    25.05.2023 12:47
    +3

    Классы-утилиты для создания структуры - это очень удобно: всякие flex, column и т.д. Если дизайнер верстает по сетке, то p-1, mr-2, gap-3 - это тоже удобно. Плюс все размерности завязаны на общий конфиг.

    Проблемы: портянки, миксы обычных классов и утилит, а также apply. Кажется, создатель tailwind считает apply большой ошибкой.

    Кмк, tailwind просто заигрался, создавая инструментарий, заменяющий чистый css в 90% случаев. В итоге его нужно учить. Он позиционируется как фреймворк, но как решать реальные проблемы использования не ясно.

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

    У quasar(vue) есть свой набор css-утилит, и вот их достаточно.


  1. gmtd
    25.05.2023 12:47

    Tailwind даёт классное комьюнити: если использовать его силу, можно реализовать практически любую идею. 

    А мне кажется, для хорошего простого понятного и мощного инструмента и комьюнити не нужна

    Вот взять хотя бы CSS3


    1. vodolazskikh Автор
      25.05.2023 12:47

      Но ведь за любым, даже очень интересно зрелым решением - огромное количество контрибьютеров и евангелистов;)


  1. noodles
    25.05.2023 12:47

    Использование таких apply-классов даёт нам возможность экспортировать целые наборы стилей.

    Использование нормальных css-классов даёт возможность экспортировать нам целые наборы свойств.)

    Использовать или нет — решать вам.

    Как руководитель - запретил его использовать впринципе. Мне нужно чтоб код был поддерживаемым через условно пять лет. И не пришлось бы нанимать синьйора-фронтендера-зайчика который меняет работу каждый год (потому что ему "надо развиваться" (с) ), и который хочет попробовать всё подряд, в том числе всратый таилвинд или микрофронтенд, но не видит результаты своих принятых решений на долгосроке (потому что уже порядочно наговнякал и пора увольнятся.))


    1. vodolazskikh Автор
      25.05.2023 12:47

      Воля ваша. Запрещать что-то просто «потому что» может быть и нормально, но каждой задаче - свои решения. Никто не говорит, что нужно использовать тот или иной фреймворк/библиотеку просто потому что, «хочется попробовать» - априори не хорошее решение. И тут уже всё равно, что подставить вместо tailwind…

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