Tailwind

Кастомізація теми через @theme у Tailwind v4

CSS-first конфігурація Tailwind v4: @theme директива замість tailwind.config.js. Кастомні кольори через OKLCH, шрифти, breakpoints, spacing, тіні та анімації. Побудова власної дизайн-системи.

Кастомізація теми через @theme у Tailwind v4

CSS-first: конфігурація, що живе у CSS

У Tailwind v4 відбулася революція у підході до конфігурації. Більше ніякого JavaScript-файлу для налаштувань. Тепер усе — у CSS.

Чому це краще? Тому що конфігурація CSS-фреймворку має бути у CSS. Коли ви відкриваєте проєкт і хочете зрозуміти його дизайн-систему — ви відкриваєте CSS-файл, а не шукаєте tailwind.config.js. Це природніше і значно менш "магічно".

Ключова директива нового підходу — @theme.


@theme — оголошення дизайн-токенів

@theme директива — це блок у вашому CSS-файлі, де ви оголошуєте дизайн-токени. Токен — це іменована одиниця вашої дизайн-системи: конкретний колір, шрифт, відступ, брейкпоінт.

Кожен токен у @theme автоматично генерує відповідні utility-класи.

@import 'tailwindcss';

@theme {
    /* Кожна CSS-змінна тут → відповідний utility-клас */
    --color-brand: oklch(0.6 0.2 270);
    /* Генерує: bg-brand, text-brand, border-brand, ... */

    --font-display: 'Satoshi', system-ui, sans-serif;
    /* Генерує: font-display */

    --breakpoint-3xl: 120rem;
    /* Генерує: 3xl: варіант */
}

Магія в тому, що Tailwind розпізнає неймспейс (--color-*, --font-*, --breakpoint-*) і знає, які утиліти генерувати.


Неймспейси @theme

Tailwind розпізнає такі неймспейси CSS-змінних і генерує для них утиліти:

CSS-змінна (неймспейс)Що генеруєтьсяПриклад утиліти
--color-{name}bg-, text-, border-, fill-, stroke-, ...bg-brand, text-primary
--font-{name}font-{name}font-sans, font-display
--text-{size}text-{size}text-hero
--breakpoint-{name}{name}: variant3xl:flex
--spacingВся spacing-шкалаp-4, m-6, gap-3
--radius-{name}rounded-{name}rounded-brand
--shadow-{name}shadow-{name}shadow-card
--animate-{name}animate-{name}animate-wiggle
--ease-{name}ease-{name} у transitionease-fluid
--blur-{name}blur-{name}blur-heavy

Кастомні кольори

Чому OKLCH?

Tailwind v4 перейшов на OKLCH (OKLab Lightness Chroma Hue) для вбудованої палітри. Розберемо, чому:

  • Перцептуальна рівномірність: зміна L на 10% дає однаковий візуальний стрибок незалежно від відтінку. У sRGB це непередбачувано.
  • Ширший кольоровий охват: P3 та Rec2020 дисплеї (Apple Retina) показують кольори яскравіше, ніж sRGB дозволяє. OKLCH охоплює і ці кольори.
  • Природне затемнення/освітлення: oklch(0.8 0.15 270) → зменш L до 0.6 → природне затемнення без зсуву відтінку.

OKLCH складається з трьох компонентів: oklch(L C H):

  • L (Lightness): від 0 (чорний) до 1 (білий)
  • C (Chroma): насиченість, від 0 (сірий) до ~0.4 (максимум)
  • H (Hue): відтінок від 0 до 360 (як у HSL)
oklch(0.6 0.2 270)
/* L=0.6 приблизно середня яскравість */
/* C=0.2 помірна насиченість */
/* H=270 фіолетовий/індиго */

Визначення кастомних кольорів

@theme {
    /* Простий бренд-колір */
    --color-brand: oklch(0.6 0.2 270);

    /* Повна палітра бренду (50-950) */
    --color-brand-50: oklch(0.97 0.03 270);
    --color-brand-100: oklch(0.93 0.06 270);
    --color-brand-200: oklch(0.88 0.1 270);
    --color-brand-300: oklch(0.8 0.14 270);
    --color-brand-400: oklch(0.71 0.18 270);
    --color-brand-500: oklch(0.6 0.22 270);
    --color-brand-600: oklch(0.52 0.22 270);
    --color-brand-700: oklch(0.43 0.2 270);
    --color-brand-800: oklch(0.34 0.17 270);
    --color-brand-900: oklch(0.26 0.13 270);
    --color-brand-950: oklch(0.17 0.09 270);
}

Тепер у вашому HTML доступні: bg-brand-500, text-brand-600, border-brand-200, ring-brand-300 тощо.

Скидання дефолтних кольорів

Якщо хочете тільки ваші кольори без вбудованої палітри Tailwind:

@theme {
    /* Видалити всі дефолтні кольори */
    --color-*: initial;

    /* Визначити тільки свої */
    --color-white: #ffffff;
    --color-black: #000000;
    --color-primary: oklch(0.6 0.22 270);
    --color-secondary: oklch(0.55 0.18 180);
    --color-accent: oklch(0.72 0.19 50);
    --color-neutral: oklch(0.5 0.01 270);
    --color-success: oklch(0.65 0.18 145);
    --color-warning: oklch(0.8 0.18 85);
    --color-error: oklch(0.6 0.22 25);
}

Це — мінімалістична дизайн-система з одним набором семантичних кольорів.

Семантичні токени: найкраща практика

Замість прив'язки до конкретного відтінку — семантичне іменування:

@theme {
    /* Базова палітра */
    --color-indigo-500: oklch(0.585 0.233 277.117);
    --color-indigo-600: oklch(0.511 0.262 276.966);
    --color-slate-900: oklch(0.129 0.042 264.695);
    --color-slate-500: oklch(0.554 0.046 257.417);

    /* Семантичні аліаси */
    --color-bg-base: oklch(1 0 0); /* white */
    --color-bg-surface: oklch(0.976 0.002 247); /* slate-50 */
    --color-text-primary: oklch(0.129 0.042 264); /* slate-900 */
    --color-text-muted: oklch(0.554 0.046 257); /* slate-500 */
    --color-accent: oklch(0.585 0.233 277); /* indigo-500 */
    --color-accent-dark: oklch(0.511 0.262 277); /* indigo-600 */
}

Тепер ваш HTML: bg-bg-base, text-text-primary, text-text-muted, bg-accent, hover:bg-accent-dark. Набагато зрозуміліше, ніж bg-white text-slate-900 text-slate-500 bg-indigo-500 hover:bg-indigo-600.


Кастомні шрифти

@theme {
    /* Sans — основний шрифт */
    --font-sans: 'Inter', system-ui, -apple-system, sans-serif;

    /* Display — для заголовків */
    --font-display: 'Clash Display', 'Inter', sans-serif;

    /* Mono — для коду */
    --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
}

Для завантаження Google Fonts — @import перед @theme:

/* Важливо: @import ПЕРЕД @theme та @import "tailwindcss" */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
@import 'tailwindcss';

@theme {
    --font-sans: 'Inter', system-ui, sans-serif;
}

Тепер font-sans використовуватиме Inter, а не системний шрифт.


Кастомні breakpoints

@theme {
    /* Додати новий breakpoint (без зміни існуючих) */
    --breakpoint-xs: 20rem; /* 320px */
    --breakpoint-3xl: 120rem; /* 1920px для ultra-wide */
    --breakpoint-4xl: 160rem; /* 2560px */
}

Використання: xs:flex, 3xl:grid-cols-6.

Щоб перевизначити існуючі breakpoints:

@theme {
    --breakpoint-*: initial; /* Скинути всі */

    --breakpoint-sm: 36rem; /* 576px */
    --breakpoint-md: 48rem; /* 768px */
    --breakpoint-lg: 64rem; /* 1024px */
    --breakpoint-xl: 80rem; /* 1280px */
}

Кастомний spacing

Найпотужніша змінна: одна --spacing визначає базову одиницю для ВСІЄЇ spacing-системи.

@theme {
    --spacing: 0.25rem; /* default — 4px */
    /* p-1 = 1 × 0.25rem = 4px */
    /* p-4 = 4 × 0.25rem = 16px */
    /* p-8 = 8 × 0.25rem = 32px */
}

/* Якщо хочете компактніший UI: */
@theme {
    --spacing: 0.2rem; /* 3.2px — щільніший */
}

/* Або просторіший: */
@theme {
    --spacing: 0.3rem; /* 4.8px — ширший */
}

Також можна додавати конкретні spacing-значення:

@theme {
    --spacing-18: 4.5rem; /* p-18, m-18, gap-18 */
    --spacing-22: 5.5rem; /* p-22, m-22, gap-22 */
    --spacing-128: 32rem; /* p-128 — дуже великий відступ */
}

Кастомні радіуси, тіні та анімації

Border Radius

@theme {
    --radius-brand: 12px; /* rounded-brand */
    --radius-xl: 1rem; /* Перевизначення існуючого xl */
    --radius-blob: 30% 70% 70% 30% / 30% 30% 70% 70%; /* Складна форма */
}

Box Shadow

@theme {
    --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.06);
    --shadow-elevated: 0 8px 24px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);
    --shadow-colored: 0 8px 24px oklch(from var(--color-brand) l c h / 0.3);
}

Анімації та keyframes

У @theme можна визначати анімації прямо з keyframes:

@theme {
    /* Анімація + keyframes разом */
    --animate-fade-in: fade-in 0.3s ease-out both;
    --animate-slide-up: slide-up 0.4s ease-out both;
    --animate-wiggle: wiggle 1s ease-in-out infinite;

    @keyframes fade-in {
        from {
            opacity: 0;
        }
        to {
            opacity: 1;
        }
    }

    @keyframes slide-up {
        from {
            opacity: 0;
            transform: translateY(12px);
        }
        to {
            opacity: 1;
            transform: translateY(0);
        }
    }

    @keyframes wiggle {
        0%,
        100% {
            transform: rotate(-3deg);
        }
        50% {
            transform: rotate(3deg);
        }
    }
}

Тепер: animate-fade-in, animate-slide-up, animate-wiggle — готові utility-класи.


@theme inline — без CSS-змінних

За замовчуванням @theme генерує CSS Custom Properties у :root і ці змінні доступні у вашому CSS. Але якщо ви не плануєте використовувати їх напряму — можна уникнути цього через @theme inline:

@theme inline {
    /* Генерує utility-класи, але НЕ додає CSS-змінні у :root */
    --color-secret: oklch(0.6 0.2 270);
}

Використовуйте @theme inline для token-значень, що використовуються тільки через Tailwind-класи, без прямого звернення var(--color-secret).


Живий приклад: повна дизайн-система

Preview
×
🔒localhost:3000

@layer у взаємодії з @theme

Крім @theme, Tailwind v4 використовує @layer base, @layer components, @layer utilities:

@import 'tailwindcss';

@theme {
    --color-brand-500: oklch(0.6 0.22 270);
    --font-sans: 'Inter', system-ui, sans-serif;
}

/* Base: глобальні стилі без класів */
@layer base {
    *,
    *::before,
    *::after {
        box-sizing: border-box;
    }

    html {
        scroll-behavior: smooth;
        -webkit-text-size-adjust: 100%;
    }

    body {
        font-family: var(--font-sans);
        color: oklch(0.15 0.02 270);
        line-height: 1.6;
    }

    h1,
    h2,
    h3,
    h4,
    h5,
    h6 {
        line-height: 1.2;
        font-weight: 700;
    }

    a {
        color: var(--color-brand-500);
        text-decoration: underline;
    }

    img {
        max-width: 100%;
        display: block;
    }
}

/* Components: компонентні класи */
@layer components {
    .card {
        @apply bg-white rounded-xl border border-slate-200 shadow-sm p-6;
    }

    .btn {
        @apply inline-flex items-center justify-center gap-2 px-5 py-2.5
               font-semibold rounded-xl transition-colors text-sm;
    }

    .btn-primary {
        @apply bg-brand-500 text-white hover:bg-brand-600;
    }
}

Порада: де зберігати теми

Для великих проєктів — розбийте CSS на логічні файли:

src/styles/
├── main.css         ← @import "tailwindcss"; @import всього нижче
├── theme/
│   ├── colors.css   ← @theme { --color-* }
│   ├── typography.css ← @theme { --font-*, --text-* }
│   └── tokens.css   ← @theme { --spacing, --radius-*, --shadow-* }
├── base.css         ← @layer base { ... }
└── components.css   ← @layer components { @utility, .btn, .card }
/* main.css */
@import 'tailwindcss';
@import './theme/colors.css';
@import './theme/typography.css';
@import './theme/tokens.css';
@import './base.css';
@import './components.css';

Чисто, зрозуміло, масштабовано.


Завдання для самоперевірки


Попередня стаття: Layout: Flexbox та GridНаступна стаття: Варіанти: hover, focus, responsive та нові v4

Copyright © 2026