Utility-first помимо Tailwind: UnoCSS, daisyUI и уроки предшественников
Tailwind — доминирующий utility-first фреймворк, и его глубокий разбор живёт в разделе 05: Tailwind CSS глубоко. Но Tailwind не появился из ниоткуда и не остался один. Его идея — атомарные классы вместо семантических — восходит к Tachyons 2014 года, а рядом с ним в 2026-м живут UnoCSS с принципиально другой архитектурой генерации и daisyUI, которая добавляет над Tailwind слой готовых компонентов. Плюс WindiCSS — предостережение о том, как быстро инструмент умирает, когда строится на чужой инфраструктуре.
Почему это важно: знание одного фреймворка делает тебя его пользователем, знание всей экосистемы — инженером, способным выбрать инструмент под задачу и не удивляться чужому коду. В этой главе разбираем, чем UnoCSS принципиально отличается от Tailwind архитектурно, как daisyUI превращает «классы-атомы» обратно в «компоненты», что гибридные схемы делают с headless-библиотеками, и по каким критериям выбирать между всем этим для pet-проекта и для SaaS с дизайн-системой.
Короткая история: от Tachyons к современности
Заголовок раздела «Короткая история: от Tachyons к современности»Tachyons (2014) — прадед жанра. Адам Морс собрал набор атомарных классов (pa2 = padding-all 0.5rem, flex, bg-blue) и доказал идею: можно верстать всё, комбинируя маленькие классы, и получать крошечный CSS за счёт переиспользования. Ограничения Tachyons: фиксированная шкала, ни конфига, ни JIT — каждый проект получал весь набор утилит.
Basscss, Shed.css, ACSS (Atomizer) — середина 2010-х, те же идеи: атомарность, вёрстка через классы. ACSS от Yahoo даже генерировал атомы on-demand из классов в разметке — но остался нишевым.
Tailwind (2017) — Адам Ватан объединил идею атомарности с полной конфигурируемостью и дизайн-токенами: шкалы цветов, отступов, типографики как данные в tailwind.config.js. С версии 3.0 (2021) — JIT-компилятор: сканирует исходники и генерирует только используемые утилиты. История успеха известна.
WindiCSS (2020–2023) — первый популярный «JIT-альтернативный» движок, совместимый с Tailwind-синтаксисом, но быстрее за счёт пропуска PostCSS-цепочки. Дальше — история-предостережение, разберём ниже.
UnoCSS: правильная архитектура on-demand
Заголовок раздела «UnoCSS: правильная архитектура on-demand»UnoCSS (Энтонью Фу, 2021) — не «ещё один набор утилит», а движок генерации: ядро не знает ни одного CSS-класса. Все утилиты приходят из пресетов — плагинов, которые декларируют правила вида «класс по regex → CSS-объявления». Три строчки из документации объясняют идею:
import { defineConfig, presetUno, presetAttributify, presetIcons } from 'unocss';
export default defineConfig({ presets: [ presetUno(), // superset: правила, совместимые с Tailwind/Windi/Bootstrap-утилитами presetAttributify(), // атрибутный синтаксис (см. ниже) presetIcons(), // 100k+ иконок как CSS-классы ], // свои правила — обычные объекты/regex: rules: [ ['text-brand', { color: '#7c3aed' }], [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })], // m-1 = .25rem ['card-shadow', { 'box-shadow': '0 2px 8px rgb(0 0 0 / 8%)' }], ], shortcuts: { // алиасы-композиции: один класс = набор утилит btn: 'px-4 py-2 rounded-lg font-medium transition-colors', 'btn-primary': 'btn bg-violet-600 text-white hover:bg-violet-700', },});Механика под капотом отличается от Tailwind радикально. Tailwind (v3+) — это JIT-компилятор, который при старте dev-сервера или сборке сканирует файлы по content-путям, находит классы, генерирует CSS и складывает в один бандл. UnoCSS — runtime-трансформация по требованию: каждый класс из разметки пропускается через матчинг правил в момент обращения, без полного сканирования проекта. На практике это даёт:
- старт dev-сервера мгновенный (нет фазы сканирования);
- генерация утилит по ходу — можно динамически собирать классы строкой и они всё равно попадут в CSS (в Tailwind это требует
safelist); - лёгкость кастомизации: своё правило — три строки в конфиге, а не плагин с AST-трансформами.
npm i -D unocssimport UnoCSS from 'unocss/vite';
export default { plugins: [UnoCSS()],};// main.ts — импорт сгенерированного CSSimport 'virtual:uno.css';Attributify-режим
Заголовок раздела «Attributify-режим»Фирменная фича UnoCSS: утилиты можно писать атрибутами, а не классами. Разметка становится чище, визуально группируется по смыслу:
<!-- Одинаковое: классная запись --><button class="px-4 py-2 rounded-lg bg-violet-600 text-white hover:bg-violet-700"> Сохранить</button>
<!-- И attributify-запись --><button px-4 py-2 rounded-lg bg-violet-600 text-white hover:bg-violet-700> Сохранить</button>Плюсы: разметку проще сканировать глазами (группы атрибутов), меньше «классовой простыни». Минусы: JSX/TS не типизируют произвольные атрибуты без дополнительной работы; некоторые линтеры ругаются; при миграции с Tailwind — новый синтаксис для команды. В UnoCSS это опциональный пресет, можно держать классный стиль.
UnoCSS против Tailwind: сравнительная таблица
Заголовок раздела «UnoCSS против Tailwind: сравнительная таблица»| Критерий | Tailwind CSS 4 | UnoCSS |
|---|---|---|
| Архитектура | Компилятор: сканирование + генерация бандла | Движок: трансформация по требованию |
| Старт dev-сервера | Сканирование content (секунды на большом проекте) | Мгновенный, генерация lazy |
| Конфигурация | theme-объект + плагины | Пресеты + правила-объекты |
| Кастомные утилиты | Плагин через matchUtilities/CSS |
Правило за 3 строки |
| Динамические классы | Требуют safelist |
Работают из коробки |
| Экосистема | Огромная: плагины, UI-киты, daisyUI | Растущая: пресеты, интеграции |
| Компонентный слой | Headless (Radix) + cva | Аналогично, shortcuts |
| CSS-in-JS интеграции | tailwind-merge, cva | uno-merge, аналоги |
| Зрелость/поддержка | Индустриальный стандарт | Активна, меньшая комьюнити |
| Лицензия | MIT | MIT |
Практический вывод: для команды и долгого продукта — Tailwind как стандарт с максимальной экосистемой. Для экспериментов, дизайн-систем с нестандартными токенами, библиотек-авторов — UnoCSS даёт гибкость, которой у Tailwind нет без борьбы с конфигом.
daisyUI: компоненты поверх Tailwind
Заголовок раздела «daisyUI: компоненты поверх Tailwind»daisyUI (Поур Сааде) решает обратную задачу: Tailwind даёт атомы, а разработчику часто нужны готовые молекулы. daisyUI — это набор готовых классов-компонентов (btn, card, modal, navbar), построенных на Tailwind-утилитах и темизируемых через CSS-переменные.
npm i -D daisyuimodule.exports = { plugins: [require('daisyui')], daisyui: { themes: ['light', 'dark', 'corporate'], // готовые темы darkTheme: 'dark', },};<!-- Компонент из атомов без единой строчки своего CSS --><div class="card w-96 bg-base-100 shadow-xl"> <div class="card-body"> <h2 class="card-title">Метрики</h2> <p>Выручка выросла на 12%.</p> <div class="card-actions justify-end"> <button class="btn btn-primary">Открыть</button> </div> </div></div>
<!-- Кнопка с состояниями и темизацией из коробки --><button class="btn btn-outline btn-error btn-sm">Удалить</button>
<!-- Модалка нативная, управляемая без JS --><button class="btn" onclick="my_modal.showModal()">Открыть</button><dialog id="my_modal" class="modal"> <div class="modal-box"> <h3>Подтверждение</h3> <div class="modal-action"> <form method="dialog"><button class="btn">Закрыть</button></form> </div> </div></dialog>Механика темизации: daisyUI генерирует набор семантических CSS-переменных (--p = primary, --pc = primary-content, --b1 = base-100 и т.д.), а каждый компонент ссылается на них. Переключение темы = замена набора переменных на [data-theme="..."]. Добавить свою тему:
daisyui: { themes: [ { mybrand: { primary: '#7c3aed', 'primary-content': '#ffffff', secondary: '#f59e0b', 'base-100': '#fafafa', // семантические слоты: primary/secondary/accent/neutral + base-100/200/300 + info/success/warning/error }, }, ],}Когда брать daisyUI:
- Нужны компоненты, но не нужен уникальный дизайн — внутренние инструменты, MVP, прототипы: скорость Bootstrap, но на современном стеке Tailwind.
- Нужна мультитемность из коробки (светлая/тёмная/брендовые) без построения дизайн-системы.
- Не нужен полный контроль над каждым пикселем: daisyUI-компоненты кастомизируются, но их структура навязывает свои классы.
Когда нет: продукт с собственным брендом и дизайн-системой (тогда headless-примитивы + свои утилиты, см. Radix/Shadcn), либо требования к pixel-perfect, которые конфликтуют с дефолтными стилями daisyUI.
WindiCSS: мёртв, и это урок
Заголовок раздела «WindiCSS: мёртв, и это урок»WindiCSS взлетел в 2020–2021 на совместимости с Tailwind v2-синтаксисом и скорости: обход PostCSS, нативная интеграция с Vite, фичи вроде variant groups (hover:(bg-red-500 text-white)). В 2023 году разработка остановилась: в пользу чего?
- Tailwind v3 встроил JIT как дефолт — главное преимущество Windi (скорость генерации) перестало быть уникальным.
- Совместимость ломалась с каждым релизом Tailwind: синтаксис-«superset» обязан бежать за апстримом, а ресурсов у проекта с одним основным мейнтейнером не хватало.
- Экосистема не успела вырасти: плагины, UI-киты, гайды писали под Tailwind.
Урок для выбора инструментов: «совместимый с X» ≠ «X». Инструмент-надстройка живёт, пока жив апстрим и пока его преимущество не поглощено. Ставить в долгоживущий продукт зависимость от нишевого мейнтейнера — риск, который надо осознавать. Сегодня WindiCSS встречается только в legacy, и миграция с него на Tailwind или UnoCSS — механическая (синтаксис близок).
Гибридные подходы: Tailwind + headless-библиотеки
Заголовок раздела «Гибридные подходы: Tailwind + headless-библиотеки»Самая популярная схема 2024–2026 для продуктов с дизайн-системой: headless-примитивы дают поведение и доступность, Tailwind — стилизацию. Это та самая архитектура Shadcn:
// Компонент = Radix-примитив (доступность, клавиатура, focus trap)// + Tailwind-классы (визуал)// + cva (варианты)import * as Dialog from '@radix-ui/react-dialog';import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva( 'inline-flex items-center justify-center rounded-lg font-medium transition-colors', { variants: { variant: { primary: 'bg-violet-600 text-white hover:bg-violet-700', outline: 'border border-violet-600 text-violet-600 hover:bg-violet-50', danger: 'bg-red-600 text-white hover:bg-red-700', }, size: { sm: 'h-8 px-3 text-sm', md: 'h-10 px-4', lg: 'h-12 px-6' }, }, defaultVariants: { variant: 'primary', size: 'md' }, });
export function ConfirmDialog() { return ( <Dialog.Root> <Dialog.Trigger className={buttonVariants()}>Удалить</Dialog.Trigger> <Dialog.Portal> <Dialog.Overlay className="fixed inset-0 bg-black/40" /> <Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-xl bg-white p-6 shadow-xl"> <Dialog.Title className="text-lg font-semibold">Точно удалить?</Dialog.Title> <Dialog.Description className="mt-2 text-sm text-slate-600"> Действие необратимо. </Dialog.Description> <div className="mt-4 flex justify-end gap-2"> <Dialog.Close className={buttonVariants({ variant: 'outline' })}> Отмена </Dialog.Close> <button className={buttonVariants({ variant: 'danger', size: 'sm' })}> Удалить </button> </div> </Dialog.Content> </Dialog.Portal> </Dialog.Root> );}Разбор радикальных примитивов, asChild, cn()-хелпера и философии Shadcn — в Radix UI и Shadcn, здесь важна роль слоёв: headless = логика и a11y, утилиты = визуальные токены, cva = вариативность без дублирования. Эта связка заменяет и CSS-in-JS по типизации, и компонентные библиотеки по контролю.
Что выбрать: pet-проект против SaaS с дизайн-системой
Заголовок раздела «Что выбрать: pet-проект против SaaS с дизайн-системой»| Критерий | Pet-проект / портфолио | SaaS с брендом и дизайн-системой |
|---|---|---|
| Главная цель | Скорость разработки, обучение | Консистентность, масштабирование, найм |
| Рекомендуемый стек | Tailwind + daisyUI (MVP-скорость) или Bootstrap | Tailwind + headless (Radix/Shadcn) + cva |
| Если хочется поэкспериментировать | UnoCSS + attributify | UnoCSS с кастомными пресетами под токены |
| Компонентный слой | daisyUI или shadcn/ui | shadcn/ui, Mantine, либо своя библиотека |
| Темизация | daisyUI-темы или data-theme |
CSS-переменные + Tailwind-токены, контрактные токены |
| Риски | Никаких, меняй сколько хочется | Зависимость от нишевых инструментов (Windi-эффект), bus factor |
Правило, которое работает почти всегда: для скорости — готовые компоненты (daisyUI, Bootstrap), для контроля — атомы + headless (Tailwind + Radix), для экспериментов — UnoCSS. И помни про связь с производительностью: размер утилитарного CSS растёт линейно с количеством уникальных классов, поэтому бюджеты из Core Web Vitals проверяются на реальном бандле.
Типичные ошибки и грабли
Заголовок раздела «Типичные ошибки и грабли»- Генерация классов строкой без safelist в Tailwind.
className={bg-${color}-500}не попадёт в сканер — класс не сгенерируется и стили пропадут. Решение в Tailwind: safelist или полные литеральные классы; в UnoCSS — работает из коробки, что частая причина переезда. - «Утилиты для всего» включая сложные анимации и многослойные селекторы. Когда класс превращается в
before:after:md:hover:bg-[...], читаемость умирает. Правило: утилиты для компоновки и типовых состояний, обычный CSS/CVA — для сложного. - daisyUI + свои тяжёлые кастомизации поверх каждого компонента. Получается слоёный пирог: daisyUI-стили → твои перекрытия →
!important. Если каждый компонент переписан, daisyUI не нужен — бери чистый Tailwind. - Миграция с WindiCSS «на досуге». Совместимость ломается, зависимости не обновляются, уязвимости копятся. Мигрируй сразу на Tailwind v3+ или UnoCSS: синтаксис близок, механика — замена пресета/плагина.
- Смешение attributify и классного синтаксиса в одном проекте без договорённости. Читаемость падает, поиск по коду усложняется. Выбери один стиль на проект.
- Ожидание, что UnoCSS — дропин-замена Tailwind с той же экосистемой. Плагины Tailwind (typography, forms) имеют аналоги, но не порты; UI-киты писаны под Tailwind-классы и с UnoCSS требуют проверки (presetUno покрывает большинство, но не всё).
- Игнорирование
content-путей в Tailwind при переезде проекта. После смены структуры папок сканер не видит новые файлы → половина стилей молча исчезает. Первое, что проверяешь при «сломавшихся стилях после рефакторинга».
Вопросы на собеседовании
Заголовок раздела «Вопросы на собеседовании»- Чем UnoCSS архитектурно отличается от Tailwind? Tailwind — компилятор: сканирует content-файлы, генерирует бандл утилит на сборке. UnoCSS — движок трансформации по требованию: классы матчатся правилами-пресетами в момент обращения, без полного сканирования, что даёт мгновенный старт и динамическую генерацию.
- Что такое attributify-режим и зачем он нужен?
Синтаксис, где утилиты пишутся HTML-атрибутами (
<button px-4 bg-red-500>) вместо классов: разметка визуально группируется по смыслу. Фирменная фича UnoCSS (presetAttributify), опциональна. - Как daisyUI темизируется? Что под капотом?
Генерирует семантические CSS-переменные (primary, base-100, info…) для каждой темы, компоненты ссылаются на них; тема переключается через
data-theme. Своя тема — объект цветов в конфиге. - Почему WindiCSS умер и какой из этого урок? Tailwind v3 поглотил главное преимущество (JIT по умолчанию), а «совместимый superset» не успевал за апстримом при ресурсах одного мейнтейнера. Урок: зависимость-надстройка над чужим синтаксисом — риск; нишевый bus factor — риск; «дропин-совместимость» не заменяет экосистему.
- Динамические классы в Tailwind: почему
bg-${color}-500не работает и что делать? Сканер ищет полные литеральные строки; интерполяция не видна на этапе генерации. Решения: safelist в конфиге, маппинг объектом ({red: 'bg-red-500'}), полные классы в данных, либо движок с runtime-генерацией (UnoCSS). - Когда daisyUI уместен, а когда — нет? Уместен: внутренние инструменты, MVP, мультитемность без дизайн-системы. Неуместен: продукт с уникальным брендом и pixel-perfect требованиями — тогда headless + свои токены.
- Что такое
cvaи какую проблему решает в связке Tailwind + headless? class-variance-authority: типизированные варианты компонента (variant × size) с дефолтами и compose. Решает проблему условных строк классов, даёт автокомплит и единый API вариативности. - Как выбрать между Tailwind, UnoCSS и daisyUI для нового проекта? Команда и долгий продукт → Tailwind (экосистема, найм). Нестандартные токены/библиотечная разработка/эксперименты → UnoCSS. Нужна скорость с готовыми компонентами и темами → Tailwind + daisyUI или Bootstrap (см. предыдущую главу).
Практика
Заголовок раздела «Практика»- Подними Vite-проект с UnoCSS: presetUno + presetAttributify + два кастомных правила (брендовый цвет, кастомная тень) и один shortcut (
btn-primary). Критерий: компонент кнопки собран и классным, и атрибутным синтаксисом, CSS содержит только используемые утилиты. - Воспроизведи граблю динамических классов: создай в Tailwind-проекте компонент с
bg-${color}-500, убедись, что стили отсутствуют. Исправь тремя способами: safelist, маппинг, полные литералы. Критерий: все три варианта работают, разница задокументирована. - Собери мини-дашборд на Tailwind + daisyUI: карточки, кнопки, таблица (если есть) с тремя темами (light/dark/своя). Критерий: переключение тем через
data-themeменяет всю палитру без перезагрузки, своя тема определена через конфиг. - Сравни бандлы: собери один и тот же лендинг на (а) Bootstrap-утилитах, (б) Tailwind, (в) UnoCSS. Замерь размер CSS в продакшене. Критерий: таблица с цифрами и объяснением различий (что генерируется, что тянется целиком).
- Рефакторни один компонент с условными строками классов на
cvaс вариантами variant/size и типизацией черезVariantProps. Критерий: вызов с неизвестным вариантом — ошибка TypeScript, автокомплит работает. - Спланируй миграцию условного проекта с WindiCSS на Tailwind v3: список несовместимостей (variant groups, shortcuts, конфиг), порядок шагов, критерии приёмки. Критерий: план, по которому можно мигрировать за один рабочий день.
Что почитать
Заголовок раздела «Что почитать»- UnoCSS: документация — пресеты, правила, shortcuts, сравнение с Tailwind в официальном гайде.
- daisyUI: документация — компоненты, темы, конфигурация.
- Tachyons — прадед жанра, рабочие примеры атомарной вёрстки.
- WindiCSS: репозиторий (архив) — readme с рекомендацией миграции на UnoCSS.
- class-variance-authority — вариативность компонентов с типизацией.
- Tailwind CSS: контент-конфигурация и safelist — механика сканирования, классическая грабля с динамическими классами.