Компоненты и варианты
Компоненты вместо @apply, варианты, cn/tailwind-merge, динамические классы.
Обновлено
Проблема
С Tailwind один и тот же набор классов (кнопка, карточка) встречается десятки раз. Как переиспользовать их, не копируя повсюду? Вариантов несколько — рекомендуется только один.
| Вариант | Пример | Вердикт |
|---|---|---|
| копипаст | те же 10 классов в 30 местах | ✗ невозможно изменить |
@apply в CSS |
.btn { @apply px-4 py-2 } |
✗ заново изобретаете классический CSS и теряете преимущества |
| компонент | <Button variant="primary"> |
✓ рекомендуется |
| циклы / map | {items.map(i => <li className="...">)} |
✓ классы встречаются в коде один раз |
Переиспользование = компоненты, а не @apply
/* ✗ заново изобретаете классический CSS и теряете преимущества */
.btn { @apply rounded-md px-4 py-2 bg-accent; }// ✓ единица переиспользования — компонент
export function Button(props) {
return <button className="rounded-md px-4 py-2 bg-accent text-white" {...props} />
}@apply годится только для стилей, которые вы не контролируете компонентами (контент из CMS, внешние библиотеки).
Варианты — базовый паттерн
Ровно то, что делает shared/ui/button.tsx в проекте:
const variants = {
primary: 'bg-accent text-accent-fg hover:bg-accent-hover',
secondary: 'border border-line-strong bg-surface hover:bg-hover',
ghost: 'text-muted hover:bg-hover',
}
const sizes = { sm: 'h-8 px-3', md: 'h-9 px-4' }
<button className={cn(base, variants[variant], sizes[size], className)} />Варианты конечны и именованы — вы не принимаете color="#f00" как prop.
cn, clsx, tailwind-merge
clsx/ простойcn— склеивает классы по условию.tailwind-merge— разрешает конфликты:twMerge('px-2', 'px-4')→'px-4'. Полезно, когда разрешаете внешнийclassName, который переопределяет стили.cva(class-variance-authority) /tailwind-variants— декларативный API для вариантов, полезен при большом числе комбинаций.
Динамические классы — ловушка
Tailwind находит классы, читая текст файлов. Собранные из частей классы он угадать не может:
`bg-${color}-500` // ✗ в итоговом CSS этого нет
const colors = { red: 'bg-red-500', blue: 'bg-blue-500' }
colors[color] // ✓ полные классы, написанные буквальноСтили из динамических данных
Значения, вычисляемые во время работы → style или CSS-переменная:
<div className="w-(--progress) bg-success" style={{ '--progress': `${pct}%` }} />Коротко
- Единица переиспользования — компонент, а не
@apply. - Конечные именованные варианты в объекте;
cnдля склейки,tailwind-mergeдля конфликтов. - Классы нужно писать в коде целиком — Tailwind не видит классы, собранные из частей.