Tailwind CSS для стилизации
Контекст
Нужна система стилизации, совместимая с Nuxt UI и компонентным подходом.
Решение
Использовать Tailwind CSS как единственную систему стилизации.
Обоснование
| Критерий | Tailwind |
|---|---|
| Nuxt UI | Нативная интеграция |
| Utility-first | Быстрая разработка |
| Design tokens | Консистентность |
| Bundle size | PurgeCSS |
| Responsive | Встроенные breakpoints |
| Dark mode | Встроенная поддержка |
Конфигурация (Tailwind v4)
В Tailwind v4 конфигурация выполняется через CSS с директивой @theme:
/* app/assets/css/main.css */
@import "tailwindcss";
@theme {
/* Кастомные spacing */
--spacing-128: 32rem;
/* Кастомные breakpoints */
--breakpoint-3xl: 1920px;
}
Важно: Кастомизация брендовых токенов (цвета, шрифты) выполняется в UI layer. См. adr-003-shared-modules.
Правила использования
Используй utility классы
<!-- Хорошо -->
<div class="flex items-center gap-4 p-4 bg-white rounded-lg shadow">
<span class="text-lg font-medium text-gray-900">Title</span>
</div>
Responsive дизайн (mobile-first)
<div class="
flex flex-col /* mobile */
md:flex-row /* tablet+ */
lg:gap-8 /* desktop+ */
">
Dark mode
Nuxt UI управляет dark mode автоматически через @nuxtjs/color-mode. Стратегия — class (не media).
<!-- Утилиты dark: применяются в тёмной теме -->
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
<!-- Для переключения темы используй Nuxt UI -->
<UColorModeButton />
Темизация через CSS variables (Tailwind v4)
В Tailwind v4 кастомные значения определяются через CSS @theme, а не через tailwind.config.ts:
/* assets/css/main.css */
@import "tailwindcss";
@theme {
--color-brand-50: oklch(98% 0.02 250);
--color-brand-500: oklch(55% 0.22 260);
--color-brand-900: oklch(25% 0.08 260);
--font-sans: 'Inter', sans-serif;
--radius-lg: 0.75rem;
}
Определённые значения сразу доступны как утилиты: bg-brand-500, text-brand-900, font-sans.
Динамическая смена темы
Для кастомных тем (например, branding по клиенту) используй CSS custom properties:
:root {
--ui-primary: var(--color-green-500);
}
.theme-blue {
--ui-primary: var(--color-blue-500);
}
Nuxt UI подхватывает --ui-primary автоматически — все компоненты обновляются без перезагрузки.
Не используй @apply
// ❌ Плохо — теряем utility-first подход
.btn {
@apply px-4 py-2 rounded bg-blue-500 text-white;
}
// ✅ Хорошо — используй Nuxt UI компонент
<UButton>Click me</UButton>
Декомпозиция компонентов вместо @apply
Если строка классов становится слишком длинной — это сигнал к декомпозиции на компоненты.
<!-- ❌ Плохо — слишком много классов в одном элементе -->
<template>
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow-md border border-gray-200 hover:shadow-lg transition-shadow duration-200">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
<UIcon name="i-heroicons-user" class="w-5 h-5 text-blue-600" />
</div>
<div class="flex flex-col">
<span class="text-sm font-medium text-gray-900">{{ user.name }}</span>
<span class="text-xs text-gray-500">{{ user.email }}</span>
</div>
</div>
<UButton size="xs" color="gray" variant="ghost">Edit</UButton>
</div>
</template>
<!-- ✅ Хорошо — декомпозиция на компоненты -->
<!-- components/UserCard.vue -->
<template>
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow-md border border-gray-200 hover:shadow-lg transition-shadow">
<UserAvatar :user="user" />
<UButton size="xs" color="gray" variant="ghost">Edit</UButton>
</div>
</template>
<!-- components/UserAvatar.vue -->
<template>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
<UIcon name="i-heroicons-user" class="w-5 h-5 text-blue-600" />
</div>
<div class="flex flex-col">
<span class="text-sm font-medium text-gray-900">{{ user.name }}</span>
<span class="text-xs text-gray-500">{{ user.email }}</span>
</div>
</div>
</template>
Правило: Если блок классов > 5-7 утилит — выносить в отдельный компонент.
Последствия
Положительные
- Быстрая разработка UI
- Консистентные spacing, colors
- Маленький production bundle
- Интеграция с Nuxt UI
Отрицательные
- Learning curve для новичков
- Длинные строки классов