Каковы лучшие практики организации Tailwind-классов в больших кодовых базах?
В больших кодовых базах: стандартизировать порядок классов через prettier-plugin-tailwindcss, организовать компонентный слой через cva/class-variance-authority, ввести дизайн-токены вместо сырых значений, настроить ESLint-плагин для запрета антипаттернов.
Лучшие практики организации Tailwind-классов в больших кодовых базах
При 50+ компонентах и команде из нескольких разработчиков Tailwind требует архитектурных решений, чтобы кодовая база оставалась поддерживаемой.
1. Автоматическая сортировка классов
Самое высокоприоритетное правило: единый порядок классов в 100% файлов через prettier-plugin-tailwindcss:
// .prettierrc
{
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindConfig": "./tailwind.config.js"
}
Плагин сортирует по официальному порядку Tailwind: positioning → display → spacing → sizing → typography → backgrounds → borders → effects → transitions → responsive.
2. Компонентный слой с class-variance-authority
// src/components/ui/badge.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { twMerge } from 'tailwind-merge';
const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors',
{
variants: {
variant: {
default: 'border-transparent bg-primary text-primary-foreground',
secondary: 'border-transparent bg-secondary text-secondary-foreground',
destructive: 'border-transparent bg-destructive text-destructive-foreground',
outline: 'text-foreground',
},
},
defaultVariants: { variant: 'default' },
}
);
interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
export function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div
className={twMerge(badgeVariants({ variant }), className)}
{...props}
/>
);
}
3. Утилита cn() как стандарт слияния классов
// src/lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
// Использование — единый паттерн по всей кодовой базе
<div
className={cn(
'base-styles',
isActive && 'active-styles',
hasError && 'error-styles',
className
)}
/>
4. Структура директорий для UI-компонентов
src/
components/
ui/ // Примитивы: Button, Input, Badge, Card
patterns/ // Составные: SearchBar, FilterPanel
layouts/ // Layout-компоненты: PageLayout, Sidebar
styles/
globals.css // @theme, @layer base
tokens.css // Дизайн-токены
5. ESLint для запрета антипаттернов
// eslint.config.js
import tailwind from 'eslint-plugin-tailwindcss';
export default [
...tailwind.configs['flat/recommended'],
{
rules: {
'tailwindcss/no-custom-classname': 'error', // без произвольных классов без @theme
'tailwindcss/no-contradicting-classname': 'error', // p-4 + p-8 одновременно
'tailwindcss/enforces-shorthand': 'warn', // px-4 py-4 → p-4
},
},
];
6. Ограничение использования @apply
@apply допустим только в globals.css для глобальных базовых стилей и в CSS Modules. В JSX/TSX-файлах — запрещён. Это предотвращает скрытые зависимости и замедление сборки.
7. Документация компонентов в Storybook
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
component: Button,
tags: ['autodocs'],
};
export default meta;
export const AllVariants: StoryObj = {
render: () => (
<div className="flex flex-wrap gap-4">
<Button intent="primary">Primary</Button>
<Button intent="secondary">Secondary</Button>
<Button intent="destructive">Destructive</Button>
</div>
),
};
Подводные камни
- Отсутствие
prettier-plugin-tailwindcssс первого дня: ретроактивное применение генерирует огромный diff без логических изменений. - Компоненты без
className-пропса: нельзя кастомизировать на месте использования. - Дублирование вариантов вручную вместо cva: изменение дефолтного стиля требует grep по всему репозиторию.
- Смешение
twMergeи простой конкатенации строк в одной кодовой базе — конфликты классов в непредсказуемых местах. - Inline
style={{}}рядом с Tailwind-утилитами для «сложных» значений — нарушение единственного источника истины. - Отсутствие lint-правил: разработчики добавляют произвольные значения (
w-[347px]) вместо стандартных токенов. - Компонентная библиотека без экспорта типов вариантов (
VariantProps) — потребители не видят доступные пропсы. - Storybook без PostCSS-конфигурации Tailwind не рендерит стили компонентов.
Common mistakes
- Смешивать «организация Tailwind-классов» с похожим механизмом без критерия выбора.
- Игнорировать риск: неверно оценить границы применения темы «организация Tailwind-классов» и получить хрупкое решение.
- Показывать только синтаксис и не объяснять поведение в runtime или сборке.
What the interviewer is testing
- Объясняет стандарты сортировки, токены, компоненты и ревью правил в больших командах.
- Показывает на примере, как работает: лучшие практики включают Prettier-сортировку, компонентные variant APIs, ограничение arbitrary values, общий token layer и документацию паттернов для повторяемых интерфейсных решений.
- Называет production-нюанс и граничный случай для темы «организация Tailwind-классов».