Реализация Utility-First CSS подхода для сайта
Utility-First — писать стили прямо в классах HTML-элементов, используя готовые утилиты: flex, items-center, text-lg, bg-blue-500. Не придумывать имена классов (.card-header-wrapper), не переключаться между HTML и CSS файлами. Tailwind CSS — реализация этого подхода.
Это работает быстрее не потому что классы красивые, а потому что CSS не растёт: каждый новый компонент переиспользует те же утилиты. Bundle CSS перестаёт линейно расти с проектом.
Установка Tailwind CSS 4
Tailwind CSS 4 полностью переработал конфигурацию — теперь всё через CSS, без tailwind.config.ts:
npm install tailwindcss @tailwindcss/vite
// vite.config.ts
import tailwindcss from '@tailwindcss/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [tailwindcss()],
})
/* src/styles/globals.css */
@import 'tailwindcss';
/* Кастомные токены прямо в CSS */
@theme {
--color-primary: #2563eb;
--color-primary-foreground: #ffffff;
--color-secondary: #f1f5f9;
--color-secondary-foreground: #0f172a;
--color-destructive: #ef4444;
--color-muted: #f8fafc;
--color-border: #e2e8f0;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
}
/* Базовые стили */
@layer base {
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: var(--font-sans);
color: var(--color-foreground);
background: var(--color-background);
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.25;
letter-spacing: -0.025em;
}
}
Tailwind CSS 3 (стабильная версия)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.ts
import type { Config } from 'tailwindcss'
export default {
content: ['./index.html', './src/**/*.{ts,tsx}'],
darkMode: ['selector', '[data-theme="dark"]'],
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#2563eb',
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
900: '#1e3a8a',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
borderRadius: {
DEFAULT: '8px',
},
boxShadow: {
card: '0 1px 3px rgb(0 0 0 / 0.08), 0 1px 2px rgb(0 0 0 / 0.04)',
},
animation: {
'fade-in': 'fade-in 200ms ease',
'slide-up': 'slide-up 300ms ease',
},
keyframes: {
'fade-in': { from: { opacity: '0' }, to: { opacity: '1' } },
'slide-up': {
from: { opacity: '0', transform: 'translateY(12px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
},
},
},
} satisfies Config
Компоненты с Tailwind
Типичная карточка:
function ProductCard({ product }: { product: Product }) {
return (
<article className="group flex flex-col bg-white rounded-xl border border-slate-200 overflow-hidden shadow-card hover:shadow-md transition-shadow duration-200">
<div className="relative aspect-video overflow-hidden bg-slate-100">
<img
src={product.image}
alt={product.name}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
/>
{product.badge && (
<span className="absolute top-3 left-3 px-2 py-0.5 text-xs font-semibold bg-primary-600 text-white rounded-full">
{product.badge}
</span>
)}
</div>
<div className="flex flex-col gap-3 p-5 flex-1">
<h3 className="font-semibold text-slate-900 leading-snug line-clamp-2">
{product.name}
</h3>
<p className="text-sm text-slate-500 line-clamp-3 flex-1">
{product.description}
</p>
<div className="flex items-center justify-between pt-2 border-t border-slate-100">
<span className="text-xl font-bold text-slate-900">
{product.price} ₽
</span>
<button className="px-4 py-2 text-sm font-medium bg-primary-600 text-white rounded-lg hover:bg-primary-700 active:scale-95 transition-all">
В корзину
</button>
</div>
</div>
</article>
)
}
@apply — вынесение в компоненты
Для повторяющихся наборов классов — @apply:
@layer components {
.btn {
@apply inline-flex items-center gap-2 rounded-lg font-medium transition-all duration-150;
@apply disabled:opacity-50 disabled:pointer-events-none;
@apply active:scale-95;
}
.btn-primary {
@apply btn bg-primary-600 text-white hover:bg-primary-700 px-4 py-2 text-sm;
}
.btn-secondary {
@apply btn bg-slate-100 text-slate-700 hover:bg-slate-200 px-4 py-2 text-sm;
}
.card {
@apply bg-white rounded-xl border border-slate-200 shadow-card p-6;
}
.input {
@apply w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm;
@apply placeholder:text-slate-400;
@apply focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent;
@apply disabled:bg-slate-50 disabled:text-slate-500;
}
}
@apply использовать умеренно — только для устойчивых паттернов. Злоупотребление возвращает к проблемам BEM/OOCSS.
Адаптивность
Tailwind использует mobile-first: без префикса — все экраны, с префиксом — от указанной ширины:
<!-- Стек на мобильных, 3 колонки от md, 4 от xl -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-3 xl:grid-cols-4">
<!-- карточки -->
</div>
<!-- Текст: маленький на мобильных, больше на десктопах -->
<h1 class="text-2xl font-bold sm:text-3xl lg:text-4xl xl:text-5xl">
Заголовок
</h1>
<!-- Скрыть на мобильных, показать от md -->
<aside class="hidden md:block w-64 shrink-0">
<!-- сайдбар -->
</aside>
Производительность
При правильной настройке content в конфиге Tailwind анализирует файлы статически и генерирует только используемые классы. Типичный результат — 8–25KB CSS в gzip для среднего проекта.
Динамические классы через конкатенацию строк (bg-${color}-500) статический анализ не поймёт. Решение — полные строки в коде:
// Неправильно — Tailwind не поймёт:
const cls = `bg-${variant === 'danger' ? 'red' : 'blue'}-500`
// Правильно:
const cls = variant === 'danger' ? 'bg-red-500' : 'bg-blue-500'
Что входит в работу
Установка и конфигурация Tailwind (v3 или v4), настройка дизайн-токенов (цвета, типографика, отступы), базовые компоненты через @apply или CVA, настройка dark mode, адаптивная сетка, рефакторинг существующих CSS при миграции.
Срок: 0.5 дня на настройку. Миграция существующего проекта — 1–3 дня в зависимости от объёма CSS.







