HTML & CSS

CSS Custom Properties. Методології. Сучасний CSS

CSS змінні (Custom Properties), var(), динамічна зміна через JS. Методології: BEM, SMACSS, OOCSS, Utility-First. Sass/SCSS огляд. Нативний CSS Nesting та сучасний CSS 2024.

CSS Custom Properties. Методології. Сучасний CSS

Як великі команди пишуть CSS без хаосу?

Уявіть: сайт на 50 сторінках, команда з 8 розробників, і дизайнер змінює основний колір бренду. Без системи — це 200 правок у 40 файлах вручну, і гарантовано кілька пропущених місць. Із правильно налаштованими CSS Custom Properties — одна зміна в :root, і весь сайт оновлений.

Але змінні — лише частина відповіді. Друга частина — методологія: спільні правила іменування та організації CSS, завдяки яким код залишається зрозумілим через рік і після того, як ти звільнився з команди.

У цій статті ми розглянемо CSS Custom Properties зсередини — не просто як "змінні", а як потужну систему з успадкуванням і динамічним оновленням. А потім — провідні методології: BEM, SMACSS, OOCSS і Utility-First.


CSS Custom Properties — змінні нового рівня

CSS Custom Properties (кастомні властивості, або CSS-змінні) — це значення, оголошені розробником, що зберігаються у CSS-каскаді і можуть використовуватися в будь-якому місці через функцію var().

На відміну від змінних у препроцесорах CSS (Sass, LESS), кастомні властивості:

  • Живуть у браузері під час виконання, а не компілюються заздалегідь
  • Підпорядковуються каскаду та успадкуванню — точно так само, як звичайні CSS-властивості
  • Можуть змінюватися через JavaScript
  • Можуть змінюватися залежно від media queries, псевдокласів, контексту

Оголошення та використання

/* Оголошення: ім'я завжди починається з -- */
:root {
    --color-primary: #6366f1;
    --color-primary-dark: #4f46e5;
    --space-sm: 0.5rem;
    --space-md: 1rem;
    --space-lg: 2rem;
    --radius: 8px;
    --font-body: system-ui, sans-serif;
}

/* Використання: var(--назва-змінної) */
.button {
    background: var(--color-primary);
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius);
    font-family: var(--font-body);
}

.button:hover {
    background: var(--color-primary-dark);
}

Зверніть: :root — псевдоклас, що відповідає кореневому елементу документа (по суті <html>). Змінні, оголошені тут, доступні скрізь у CSS.

Fallback-значення

var() приймає другий аргумент — запасне значення, якщо змінна не визначена:

.element {
    /* Якщо --color-accent не визначена — використає #6366f1 */
    color: var(--color-accent, #6366f1);

    /* Вкладені fallback */
    padding: var(--space-md, var(--space-sm, 1rem));
}

Це особливо корисно у компонентах бібліотеки: компонент може мати власні дефолти, але дозволяти замінювати їх через змінні.


Область видимості та успадкування

CSS Custom Properties підпорядковуються каскаду CSS — вони успадковуються від батьківських елементів до дочірніх. Це суттєво відрізняє їх від змінних Sass, які компілюються у статичні значення.

/* Глобальні значення */
:root {
    --color: #1e293b;
    --bg: white;
}

/* Локальне перевизначення для конкретного контексту */
.dark-section {
    --color: #f1f5f9;
    --bg: #0f172a;
}

/* Всі дочірні елементи .dark-section отримають локальні значення */
.dark-section p {
    color: var(--color); /* #f1f5f9, бо успадкували від .dark-section */
    background: var(--bg);
}
Preview
×
🔒localhost:3000

Кожна картка використовує одні й ті самі класи (.scope-btn, .scope-card h3), але отримує різні значення завдяки локальному перевизначенню змінних на рівні .scope-card--dark та .scope-card--accent.


Dark Mode через Custom Properties

Найпотужніше застосування кастомних властивостей — тема (light/dark mode). Замість тисяч рядків перевизначень у @media (prefers-color-scheme: dark), достатньо змінити значення змінних в одному місці:

/* Світла тема — за замовчуванням */
:root {
    --bg-base: #ffffff;
    --bg-surface: #f8fafc;
    --bg-elevated: #f1f5f9;

    --text-primary: #0f172a;
    --text-secondary: #475569;
    --text-muted: #94a3b8;

    --border-default: #e2e8f0;
    --border-strong: #cbd5e1;

    --accent: #6366f1;
    --accent-hover: #4f46e5;
    --accent-text: #ffffff;

    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
    --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* Темна тема — тільки перевизначення змінних! */
@media (prefers-color-scheme: dark) {
    :root {
        --bg-base: #0f172a;
        --bg-surface: #1e293b;
        --bg-elevated: #334155;

        --text-primary: #f1f5f9;
        --text-secondary: #cbd5e1;
        --text-muted: #64748b;

        --border-default: #334155;
        --border-strong: #475569;

        --accent: #818cf8;
        --accent-hover: #a5b4fc;
        --accent-text: #1e293b;

        --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
        --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
    }
}

/* Компоненти — використовують тільки змінні, жодних конкретних кольорів */
.card {
    background: var(--bg-surface);
    border: 1px solid var(--border-default);
    box-shadow: var(--shadow-sm);
    color: var(--text-primary);
}

.btn-primary {
    background: var(--accent);
    color: var(--accent-text);
}
.btn-primary:hover {
    background: var(--accent-hover);
}

Результат: вся темна тема в 15 рядках змінних, а не в тисячах рядків перевизначень.

Ручне перемикання теми через JavaScript

Для кнопки "Перемкнути тему" без залежності від системних налаштувань:

// Читання поточної теми
const theme = document.documentElement.dataset.theme // 'light' | 'dark'

// Перемикання
function toggleTheme() {
    const isDark = document.documentElement.dataset.theme === 'dark'
    document.documentElement.dataset.theme = isDark ? 'light' : 'dark'
    localStorage.setItem('theme', isDark ? 'light' : 'dark')
}
/* CSS: стилізація через data-атрибут */
:root[data-theme='dark'] {
    --bg-base: #0f172a;
    --text-primary: #f1f5f9;
    /* ... */
}

Динамічна зміна через JavaScript

CSS Custom Properties можна читати та змінювати через JavaScript — це дозволяє робити інтерактивні теми, динамічні кольори, просунуте управління станом прямо через CSS:

const root = document.documentElement

// Читання змінної
const primaryColor = getComputedStyle(root).getPropertyValue('--color-primary').trim()

// Запис змінної
root.style.setProperty('--color-primary', '#ec4899')

// Скидання до значення зі стилів
root.style.removeProperty('--color-primary')

Практичний кейс — кастомізатор теми для користувача:

// Color picker змінює CSS-змінну в реальному часі
document.querySelector('#color-picker').addEventListener('input', (e) => {
    document.documentElement.style.setProperty('--accent', e.target.value)
})

Без Java Script таке було б неможливо без повного перезавантаження стилів.


Препроцесори CSS: Sass/SCSS — навіщо і чи потрібні зараз?

Sass (Syntactically Awesome Stylesheets) та його синтаксис SCSS з'явились у 2006 році, щоб вирішити обмеження CSS того часу: відсутність змінних, вкладення, функцій та міксинів. Впродовж десятиліття Sass був невід'ємною частиною front-end розробки.

Сьогодні нативний CSS закрив більшість цих потреб:

Функція SassНативний CSS аналогПідтримка
Змінні $color: redCustom Properties --color: redВсі сучасні браузери
Вкладення (&:hover)CSS NestingChrome 112+, Firefox 117+
@import файлів@layer + @importСтандарт
Функції кольорівcolor-mix(), oklch()Chrome 111+, Firefox 113+
@mixin та @includeНемає прямого аналогу

Коли Sass досі корисний?

  • Великі legacy-проєкти, де весь код вже на Sass
  • Складні математичні функції (lighten(), darken(), mix()) — хоча color-mix() частково замінює їх
  • Компілятори тем — де потрібно генерувати велику кількість CSS-правил лише при складанні
  • Teams зі строгою структурою файлів через @use та @forward модульну систему Sass
Для нових проєктів у 2024 — Sass часто надлишковий. CSS Custom Properties + @layer + нативний CSS Nesting покривають 80-90% того, що раніше вимагало Sass. Вивчте нативний CSS у першу чергу.

Методології CSS: навіщо вони потрібні?

CSS — найбільш "вільна" мова у фронтенді. Немає компілятора, що перевіряє логіку. Немає namespace. Будь-який клас може вплинути на будь-який елемент. У маленькому проєкті це зручна гнучкість. У великому — джерело хаосу.

Методологія CSS — це набір конвенцій: як іменувати класи, як організовувати файли, як забезпечити, щоб стилі одного компонента не ламали інший. Це не технічне обмеження, а командний договір.

Loading diagram...
graph LR
    A["Без методології\n(500+ файлів)"] --> B["!important скрізь"]
    A --> C["Конфлікти стилів"]
    A --> D["Страх змінювати CSS"]
    E["З методологією"] --> F["Передбачувані імена"]
    E --> G["Ізоляція компонентів"]
    E --> H["Легкий рефакторинг"]
    style A fill:#ef4444,color:#fff
    style E fill:#10b981,color:#fff

Розглянемо три найвпливовіші методології.


BEM — Block, Element, Modifier

BEM (Block Element Modifier) — методологія, розроблена командою Яндекс у 2009 році. Вона надає чіткі правила іменування класів, що самодокументуються.

Концепція

Block (блок) — самодостатній, незалежний компонент. Блок має сенс сам по собі: card, button, header, menu.

Element (елемент) — частина блока, що не має самостійного значення поза блоком. Елемент позначається через подвійне підкреслення: card__title, button__icon, menu__item.

Modifier (модифікатор) — стан або варіація блока чи елемента. Позначається через подвійне тире: button--primary, card--featured, menu__item--active.

/* BEM: назва каже сама за себе */

/* Блок */
.card { ... }

/* Елементи блока */
.card__image { ... }
.card__body { ... }
.card__title { ... }
.card__description { ... }
.card__footer { ... }
.card__button { ... }

/* Модифікатори блока */
.card--featured { ... }
.card--compact { ... }

/* Модифікатори елемента */
.card__button--disabled { ... }
<!-- BEM HTML — структура читається без CSS -->
<article class="card card--featured">
    <img class="card__image" src="..." alt="..." />
    <div class="card__body">
        <h2 class="card__title">Заголовок</h2>
        <p class="card__description">Опис...</p>
    </div>
    <footer class="card__footer">
        <button class="card__button card__button--primary">Купити</button>
        <button class="card__button card__button--secondary">Зберегти</button>
    </footer>
</article>

Переваги BEM

  1. Самодокументованість: з назви класу зрозуміло, де він живе і до чого належить.
  2. Низька специфічність: лише один клас, без вкладених селекторів — намного простіше перевизначати.
  3. Переносність: блок можна скопіювати в будь-яке місце сторінки без побічних ефектів.
  4. Scalability: зрозуміло навіть у великих командах.

Поширені помилки з BEM

/* ❌ Погано: вкладений селектор порушує незалежність */
.card .card__title { color: red; }

/* ✅ Добро: завжди один клас */
.card__title { color: red; }

/* ❌ Погано: елемент елемента (BEM не дозволяє) */
.card__body__title { ... }

/* ✅ Добро: flatten — елемент завжди блоку, не іншому елементу */
.card__body-title { ... }
/* або */
.card__title { ... }
Preview
×
🔒localhost:3000

BEM у деталях: синтаксис, правила та реальні компоненти

Базовий синтаксис BEM ми вже розглянули. Тепер заглибимося в деталі, які важливі у реальних проєктах — ті нюанси, що стають на заваді, коли теорія зустрічається з практикою.

Правила формування імен у BEM

Правило 1: Тільки класи, ніяких тегів та ідентифікаторів.

BEM категорично відмовляється від селекторів тегів (.article h2) та ідентифікаторів (#header). Причина проста: клас — це інтерфейс між HTML та CSS. Тег прив'язує стиль до конкретного HTML-елемента, що знищує переносність. Ідентифікатор підвищує специфічність настільки, що перевизначення стає складним.

/* ❌ BEM забороняє: стиль прив'язаний до тегу */
.nav ul {
    list-style: none;
}
.nav li a {
    color: #333;
}

/* ✅ BEM вимагає: кожен елемент отримує клас */
.nav__list {
    list-style: none;
}
.nav__link {
    color: #333;
}

Правило 2: Елемент належить блоку — завжди і тільки йому.

Елемент card__title — це завжди елемент блоку card. Він не може бути "елементом елемента". Якщо вам здається, що потрібна така конструкція — це сигнал виділити внутрішню структуру в окремий блок.

<!-- ❌ Ілюзія: елемент елемента -->
<div class="card">
    <div class="card__body">
        <h2 class="card__body__title">Заголовок</h2>
    </div>
</div>

<!-- ✅ BEM: елемент — завжди блоку, DOM-вкладення не впливає на назву -->
<div class="card">
    <div class="card__body">
        <h2 class="card__title">Заголовок</h2>
    </div>
</div>

Правило 3: Модифікатор не існує сам по собі — тільки разом із блоком/елементом.

Модифікатор визначає варіацію або стан. Він завжди використовується разом з базовим класом, а не замість нього.

<!-- ❌ Тільки модифікатор без базового класу -->
<button class="btn--primary">Кнопка</button>

<!-- ✅ Базовий клас + модифікатор -->
<button class="btn btn--primary">Кнопка</button>

Чому? Бо btn--primary визначає лише відмінність від базового btn. Базова структура (padding, border-radius, cursor) описана в .btn. Якщо взяти тільки модифікатор — кнопки не буде.

Правило 4: Kebab-case всередині імені блоку/елемента/модифікатора.

Якщо ім'я блоку складається з кількох слів — використовуйте - (тире), не _ (підкреслення), не camelCase:

/* ✅ Правильно */
.search-form {
}
.search-form__input {
}
.search-form__button--disabled {
}

/* ❌ Неправильно */
.searchForm {
}
.search_form__input {
}
.SearchForm__Button--Disabled {
}

Підкреслення як роздільник зарезервовано для BEM-синтаксису (__ — елемент, -- — модифікатор).


BEM та JavaScript: стани та JS-хукі

Один із найважливіших патернів у BEM — розмежування між CSS-класами для стилізації та JS-хукі для поведінки.

Проблема: якщо JavaScript шукає кнопку через .btn--primary і ви вирішите перейменувати клас — поведінка зламається. BEM для цього рекомендує окремий префікс js-:

<!-- js- класи — тільки для JavaScript, ніяких стилів! -->
<button class="btn btn--primary js-submit-form">Зберегти</button>
// JavaScript використовує js- клас, незалежний від CSS
document.querySelector('.js-submit-form').addEventListener('click', submit)
/* CSS ніколи не використовує js- класи */
.btn--primary {
    background: #6366f1;
    color: white;
}
/* js-submit-form тут не згадується */

Тепер ви можете безпечно рефакторити CSS (btn--primarybtn--cta) без ризику зламати JavaScript.

Стани через BEM-модифікатори:

Динамічні стани (відкрито/закрито, активно/неактивно, завантаження) в BEM виражаються через модифікатори, що додаються або видаляються через JavaScript:

// Відкрити dropdown
const dropdown = document.querySelector('.dropdown')
dropdown.classList.add('dropdown--open')

// Закрити
dropdown.classList.remove('dropdown--open')

// Перемкнути
dropdown.classList.toggle('dropdown--open')
.dropdown {
    display: none;
} /* За замовчуванням */
.dropdown--open {
    display: block;
} /* Стан: відкрито */
.dropdown__item--active {
    font-weight: bold;
} /* Стан: активний пункт */
.btn--loading {
    opacity: 0.7;
    cursor: wait;
} /* Стан: завантаження */
Деякі команди використовують is- або has- стани (прийнятий у SMACSS), а не BEM-модифікатори. Наприклад: <div class="dropdown is-open">. Це допустимо — головне, що правило в команді одне і дотримується всіма.

Реальний компонент 1: Кнопки у BEM

Кнопка — найбільш перевикористовуваний компонент. Подивимося, як побудувати систему кнопок через BEM, яку можна масштабувати:

Preview
×
🔒localhost:3000

Зверніть на ключові рішення:

  • Block .btn — базова структура без кольору, це не пов'язана з конкретним варіантом.
  • Elements .btn__icon, .btn__label, .btn__spinner — частини кнопки.
  • Modifiers кольору .btn--primary, .btn--danger — незалежні варіанти.
  • Modifiers розміру .btn--sm, .btn--lg — ортогональні: можна комбінувати з будь-яким кольором.
  • Стани .btn--loading, :disabled — визначають поведінку.

Жоден модифікатор не копіює базові правила .btn — він лише додає або перевизначає конкретні властивості.


Реальний компонент 2: Навігаційне меню

Навігація — ідеальний приклад для ілюстрації вкладених блоків у BEM:

Preview
×
🔒localhost:3000

Зверніть: у навігації два вкладених блокиnavbar і dropdown. Вони незалежні:

  • dropdown може використовуватися де завгодно, не лише в navbar
  • navbar не знає про внутрішню структуру dropdown
  • Блок btn повторно використовується в navbar__actions — це сила BEM: переносні блоки

Реальний компонент 3: Форма з валідацією

Форма — класичний кейс з елементами, модифікаторами станів і суміщенням BEM-блоків:

Preview
×
🔒localhost:3000

Форма ілюструє потужність BEM-модифікаторів для станів:

  • .form-field--error і .form-field--success — змінюють колір рамки поля через один клас на батьківському елементі
  • .form-field__label--required — елемент з модифікатором додає зірочку через CSS ::after
  • Блок btn повторно використовується без жодних змін

BEM та файлова структура

Стандартна BEM-структура проєкту — один файл на блок. Це забезпечує легке знаходження коду і незалежне тестування:

styles/
├── blocks/
│   ├── btn.css          → стилі блоку .btn та всіх його елементів/модифікаторів
│   ├── card.css
│   ├── navbar.css
│   ├── dropdown.css
│   ├── form-field.css
│   ├── auth-form.css
│   ├── modal.css
│   └── badge.css
├── base/
│   ├── reset.css
│   └── typography.css
└── index.css            → @import всіх блоків
/* index.css */
@layer reset, base, blocks;

@layer reset {
    @import 'base/reset.css';
}
@layer base {
    @import 'base/typography.css';
}

@layer blocks {
    @import 'blocks/btn.css';
    @import 'blocks/card.css';
    @import 'blocks/navbar.css';
    @import 'blocks/dropdown.css';
    @import 'blocks/form-field.css';
    @import 'blocks/auth-form.css';
    @import 'blocks/modal.css';
}

Переваги такої структури:

  • Легко знайти: .dropdown--openblocks/dropdown.css
  • Легко видалити компонент: видалив файл і рядок в index.css
  • Конфліктів немає: два розробники рідко торкаються одного файлу

BEM + CSS Custom Properties = потужна система

Поєднання BEM та Custom Properties дозволяє будувати параметризовані компоненти — блок з "ручками" для кастомізації:

/* Block: badge */
.badge {
    /* "Ручки" компонента — Custom Properties з дефолтами */
    --badge-color: #475569;
    --badge-bg: #f1f5f9;
    --badge-border: #e2e8f0;

    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.2rem 0.6rem;
    border: 1px solid var(--badge-border);
    border-radius: 20px;
    background: var(--badge-bg);
    color: var(--badge-color);
    font-size: 0.72rem;
    font-weight: 600;
}

/* Модифікатори лише перевизначають "ручки" */
.badge--primary {
    --badge-color: #4338ca;
    --badge-bg: #e0e7ff;
    --badge-border: #c7d2fe;
}

.badge--success {
    --badge-color: #15803d;
    --badge-bg: #dcfce7;
    --badge-border: #bbf7d0;
}

.badge--danger {
    --badge-color: #b91c1c;
    --badge-bg: #fee2e2;
    --badge-border: #fecaca;
}

/* Повна кастомізація ззовні без модифікатора */
.notification-panel .badge {
    --badge-color: white;
    --badge-bg: #6366f1;
    --badge-border: transparent;
}

Це елегантне рішення: модифікатори не дублюють CSS-правила — вони лише оголошують нові значення для Custom Properties.


Антипатерни BEM: що НЕ робити


Чеклист перед написанням нового BEM-компонента

Перш ніж створити новий компонент у BEM — дайте відповіді на ці питання:

  1. Чи є цей компонент блоком або елементом?
    • Блок: має сенс сам по собі, може існувати в різних контекстах
    • Елемент: тільки всередині конкретного блоку, без нього не має сенсу
  2. Чи існує вже схожий блок, який можна розширити модифікатором?
    • Новий блок feature-card чи модифікатор card--feature?
  3. Які стани може мати блок?
    • Перелічіть всі: .block--loading, .block--empty, .block--error, .block--disabled
  4. Як цей блок буде кастомізуватися ззовні?
    • Through CSS Custom Properties? Через модифікатори? Через передачу класів?
  5. Які елементи мають публічний API (можуть використовуватися ззовні)?
    • А які — суто внутрішні деталі реалізації?

SMACSS — Scalable and Modular Architecture for CSS

SMACSS (Scalable and Modular Architecture for CSS) — методологія Джонатана Снука, заснована на категоризації стилів за типом, а не за компонентом.

SMACSS розділяє CSS на 5 категорій:

Base

Стилі для HTML-елементів без класів. Reset, нормалізація, базова типографіка.

body {
    font-family: system-ui;
}
h1,
h2 {
    line-height: 1.2;
}
a {
    color: inherit;
}

Layout

Великі структурні секції: header, footer, sidebar, main. Зазвичай префікс l-.

.l-header {
    position: sticky;
    top: 0;
}
.l-sidebar {
    width: 260px;
}
.l-main {
    flex: 1;
}

Module

Перевикористовувані компоненти: картки, кнопки, форми, навігація. Основна категорія.

.card {
    border-radius: 8px;
}
.btn {
    padding: 0.5rem 1rem;
}

State

Стани елементів. Зазвичай префікс is- або has-. Часто додаються через JavaScript.

.is-active {
    display: block;
}
.is-hidden {
    display: none;
}
.has-error {
    border-color: red;
}

Theme

Кольорові схеми та візуальні варіації. Зазвичай задаються через CSS Custom Properties.

.theme-dark {
    --bg: #1e293b;
}
.theme-brand {
    --accent: #6366f1;
}

SMACSS менш суворий за BEM у синтаксисі, але дає чітку концептуальну карту файлів. Типова структура:

styles/
    base/          → reset.css, typography.css
    layout/        → header.css, sidebar.css, grid.css
    modules/       → card.css, button.css, form.css
    state/         → states.css
    theme/         → dark.css, brand.css

OOCSS — Object-Oriented CSS

OOCSS (Object-Oriented CSS) — методологія Ніколь Салліван, заснована на двох принципах:

Принцип 1: Відокремлення структури від зовнішнього вигляду (Structure from Skin). Структурні властивості (розміри, відступи, позиціонування) і візуальні (кольори, шрифти, фони) — в окремих класах:

/* Структура */
.btn {
    display: inline-flex;
    padding: 0.5rem 1rem;
    border-radius: 6px;
}

/* Зовнішній вигляд (skin) */
.btn-primary {
    background: #6366f1;
    color: white;
}
.btn-danger {
    background: #ef4444;
    color: white;
}
.btn-outline {
    border: 2px solid currentColor;
    background: transparent;
}

Принцип 2: Відокремлення контейнера від контенту (Container from Content). Стилі елемента не залежать від місця його розміщення:

/* ❌ OOCSS табу: .sidebar h2 — прив'язка до контейнера */
.sidebar h2 {
    font-size: 1rem;
}

/* ✅ OOCSS: клас на самому елементі */
.section-title {
    font-size: 1rem;
}

OOCSS — основа для таких фреймворків, як Bootstrap, де кнопка — це class="btn btn-primary btn-lg" (структура + skin + розмір).


Utility-First CSS — підхід Tailwind

Utility-First (утилітарний підхід) — повна протилежність BEM. Замість семантичних класів-компонентів — атомарні низькорівневі класи, кожен з яких робить одну річ:

<!-- BEM -->
<button class="btn btn--primary btn--large">Купити</button>

<!-- Utility-First (Tailwind-style) -->
<button
    class="inline-flex items-center px-6 py-3 bg-indigo-500 text-white
               font-semibold rounded-lg hover:bg-indigo-600 transition-colors"
>
    Купити
</button>

Переваги

  • Немає проблем з іменуванням: не треба придумувати назву .card--featured-with-image-landscape
  • Жоден CSS-файл не росте: ви використовуєте існуючі utility-класи, не додаєте нові
  • Швидкість прототипування: все стилізується прямо в HTML без перемикання файлів

Недоліки

  • HTML захаращується довгими рядками класів
  • Повторення: одна й та сама комбінація 20+ класів у 50 місцях — складно змінювати
  • Необхідність tax-плагіна (або PurgeCSS) для продакшну, щоб не включати весь CSS фреймворку

Tailwind вирішує проблему дублювання через директиву @apply і компонентний підхід React/Vue.

Порівняння підходів

<div class="card card--featured">
    <img class="card__image" src="..." />
    <div class="card__body">
        <h2 class="card__title">Заголовок</h2>
        <p class="card__text">Текст</p>
    </div>
</div>
.card {
    border-radius: 8px;
    background: white;
}
.card--featured {
    border: 2px solid #6366f1;
}
.card__image {
    width: 100%;
}
.card__body {
    padding: 1.25rem;
}
.card__title {
    font-size: 1.1rem;
    color: #1e293b;
}
.card__text {
    color: #64748b;
}
  • ✅ Читабельний HTML
  • ✅ Маленький CSS-файл (при правильному використанні)
  • ⚠️ Треба придумувати назви

Нативний CSS Nesting

Донедавна вкладення стилів було ексклюзивом Sass. У 2023 році нативний CSS Nesting отримав підтримку в усіх major-браузерах.

Нативне вкладення дозволяє писати стилі для дочірніх елементів та псевдокласів всередині батьківського правила:

/* Sass (або старий підхід) */
.card {
    background: white;
}
.card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card .card__title {
    font-size: 1.1rem;
}
.card .card__title:first-child {
    margin-top: 0;
}

/* Нативний CSS Nesting (Chrome 112+, Firefox 117+, Safari 17.2+) */
.card {
    background: white;

    &:hover {
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }

    .card__title {
        font-size: 1.1rem;

        &:first-child {
            margin-top: 0;
        }
    }
}

& — посилання на батьківський селектор. Без &:

.nav {
    display: flex;

    /* Вкладення БЕЗ & — еквівалентно .nav a */
    a {
        color: inherit;
        text-decoration: none;
    }

    /* Вкладення З & — модифікатор або псевдоклас */
    &.is-open {
        display: block;
    }
    &:focus-within {
        outline: 2px solid var(--accent);
    }
}
Важлива різниця: у нативному CSS Nesting, на відміну від Sass, некваліфікований тег-селектор у вкладенні (наприклад, p, a) вимагає& або розміщення після явного селектора. Просто p { ... } у вкладенні — валідно, але деякі старіші версії Chrome/Firefox обробляли це по-різному. Найбезпечніше: використовуйте & p або & a.

Нативний Nesting + BEM

/* BEM через нативний Nesting — читабельно і без Sass */
.card {
    background: var(--bg-surface);
    border-radius: var(--radius);
    overflow: hidden;

    /* Модифікатори */
    &--featured {
        border: 2px solid var(--accent);
    }

    /* Елементи */
    &__image {
        width: 100%;
        display: block;
    }

    &__body {
        padding: var(--space-md);
    }

    &__title {
        font-size: 1.1rem;
        color: var(--text-primary);
        margin: 0 0 var(--space-sm);
    }

    /* Ефекти */
    &:hover {
        box-shadow: var(--shadow-md);
    }
}

CSS Reset vs Normalize vs Modern CSS Reset

Кожен проєкт починається з вирішення: як обнулити стилі браузера?

Класичний Reset (Eric Meyer, 2011): скидає все — margin, padding, font-size. Занадто агресивний, треба відновлювати кожну базову стилізацію вручну.

Normalize.css (Necolas, 2012): не скидає, а вирівнює різницю між браузерами, зберігаючи корисні дефолти. Менш агресивний.

Modern CSS Reset (Andy Bell, 2023): сучасний підхід — мінімальний набір правил з глибоким розумінням сучасних браузерів:

/* Modern CSS Reset — поясняємо кожне правило */

/* 1. Box-sizing: border-box — padding/border не збільшують елемент */
*,
*::before,
*::after {
    box-sizing: border-box;
}

/* 2. Скасувати дефолтні margin (найбільше джерело "чому є відступ?") */
* {
    margin: 0;
}

/* 3. body: мінімальна висота viewport, гарне рендерення шрифтів */
body {
    min-height: 100dvh;
    -webkit-font-smoothing: antialiased;
}

/* 4. Медіа-елементи — адаптивні за замовчуванням */
img,
picture,
video,
canvas,
svg {
    display: block;
    max-width: 100%;
}

/* 5. Форми успадковують шрифт (не системний serif) */
input,
button,
textarea,
select {
    font: inherit;
}

/* 6. Заголовки та параграфи — не виходять за межі контейнера */
p,
h1,
h2,
h3,
h4,
h5,
h6 {
    overflow-wrap: break-word;
}

/* 7. Canvas — не "тягне" за межі */
canvas {
    display: block;
    max-width: 100%;
}

/* 8. Доступна анімація для людей з reduce motion */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }
}

Резюме: сучасний CSS стек

Loading diagram...
mindmap
    root((Сучасний CSS))
        Custom Properties
            Глобальні в :root
            Локальні override
            Dark Mode через змінні
            JS: setProperty / getPropertyValue
        Організація
            BEM: Block__Element--Modifier
            SMACSS: Base/Layout/Module/State
            OOCSS: Structure + Skin
            Utility-First
        Сучасні інструменти
            Нативний CSS Nesting
            @layer каскадні шари
            @scope локальний scope
            color-mix та oklch
        Препроцесори
            Sass: корисний у legacy
            Нові проєкти: нативний CSS

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


Попередня стаття: CSS Анімації та Переходи

Наступна стаття: CSS Pseudo-classes та Pseudo-elements

Copyright © 2026