nmk

Лекція №16 (2 години). Стилізація та дизайн-системи: Архітектура CSS в епоху компонентів

План лекції

  1. Проблема глобального простору імен (Global Scope) у класичному CSS.
  2. Локальна ізоляція: CSS Modules як нативний підхід збірників (Bundlers).
  3. Парадигма CSS-in-JS (Styled Components, Emotion): Динамічна стилізація на основі пропсів.
  4. Парадигма Utility-first CSS (Tailwind CSS): Атомарний підхід та продуктивність.
  5. Дизайн-системи та бібліотеки компонентів (Material UI, Ant Design, Chakra UI).
  6. Headless UI бібліотеки (Radix UI, Headless UI) як новий інженерний стандарт доступності (Accessibility).
  7. Методологія вибору стеку стилізації для Enterprise-проєктів.

Перелік умовних скорочень

Вступ

Огляд підходів до стилізації: CSS Modules, CSS-in-JS (Styled Components) та Utility-first CSS (Tailwind CSS). Розглядається роль бібліотек компонентів, таких як Material UI або Ant Design, у прискоренні розробки корпоративних систем, а також сучасні підходи Headless UI.

Перехід від написання класичних HTML-сторінок до розробки ізольованих React-компонентів фундаментально змінив підхід до написання CSS. У класичній веб-розробці всі стилі є глобальними. Якщо два розробники у великій команді випадково створять клас .button в різних файлах, браузер неминуче об’єднає їх, спричинивши візуальний конфлікт (CSS Specificity Wars). Раніше це вирішували суворими конвенціями іменування (методологія BEM: .button__text--active). Сьогодні компонентна архітектура вимагає програмної (архітектурної) ізоляції стилів. У цій лекції ми розберемо еволюцію стилізації в React: від генерації унікальних хешів у модулях до атомарного CSS та Headless-компонентів.


1. Проблема Глобального Scope та Конфлікти стилів

Якщо ви імпортуєте звичайний CSS файл у свій React-додаток:

import "./Button.css"; // Містить клас .title { color: red; }
import "./Header.css"; // Містить клас .title { color: blue; }

Збірник (Webpack/Vite) просто “склеїть” їх у єдиний <style> тег у секції <head>. Внаслідок каскадування (Cascading), останній імпортований клас перепише попередній, і всі елементи з класом title у всьому додатку стануть синіми. Ця проблема є критичною для Enterprise-рішень.


2. Локальна ізоляція: CSS Modules

CSS Modules — це не нова мова, це конфігурація збірника (булд-кроку). Вона перетворює класи на об’єкти JavaScript та генерує для них унікальні криптографічні хеші.

Файл: Button.module.css

/* Ми пишемо просте коротке ім'я, не боючись конфліктів */
.successBtn {
  background-color: green;
}

Файл: Button.jsx

// styles - це JS-об'єкт, ключами якого є імена класів з файлу
import styles from "./Button.module.css";

const Button = () => {
  // В консолі браузера ми побачимо згенерований клас: class="Button_successBtn__3f9d2"
  return <button className={styles.successBtn}>Зберегти</button>;
};

Переваги: Повна гарантія відсутності конфліктів (Scoped CSS). Підтримка всіх фіч нативного CSS (Sass/Scss). Недоліки: Динамічна стилізація (на основі стейту чи пропсів компонента) вимагає громіздкої конкатенації рядків: className={isActive ? styles.active : styles.default} (для чого часто використовують бібліотеку classnames або clsx).


3. Парадигма CSS-in-JS (Styled Components)

CSS-in-JS — це архітектурний підхід, де стилі пишуться безпосередньо всередині JavaScript-файлів за допомогою шаблонних ліній (Tagged Template Literals). Найпопулярніша бібліотека — Styled Components.

Філософія Styled Components: “Не стилізуйте теги, створюйте стилізовані компоненти”.

import styled from "styled-components";

// Ми створюємо НОВИЙ React компонент (<button>), який вже має вшиті стилі
// Інтерполяція дозволяє читати Props!
const SubmitButton = styled.button`
  background-color: ${(props) => (props.primary ? "blue" : "gray")};
  color: white;
  padding: 10px 20px;
  border-radius: 4px;

  /* Вбудована підтримка псевдокласів (як у Sass) */
  &:hover {
    opacity: 0.8;
  }
`;

const Form = () => {
  return (
    <div>
      <SubmitButton primary>Відправити</SubmitButton>
      <SubmitButton>Скасувати</SubmitButton>
    </div>
  );
};

Переваги: Неймовірна гнучкість завдяки доступу до Props та Theme Provider. Область видимості (scope) інкапсульована на рівні компонента. Недоліки: Зниження продуктивності в Runtime. Парсинг JS-коду, щоб згенерувати CSS-клас та вставити його в DOM, займає час процесора (особливо помітно на великих списках). Тому цей підхід заборонено використовувати в Server Components (Next.js 13+).


4. Парадигма Utility-first CSS: Tailwind CSS

Utility-first CSS (Атомарний підхід) — це найпотужніший сучасний індустріальний тренд. Замість того, щоб придумувати ім’я класу .profile-card і писати для нього 10 CSS-властивостей, ви використовуєте 10 маленьких (атомарних) класів, кожен з яких робить лише ОДНУ дію.

Лідером цього підходу є Tailwind CSS.

// Ми напряму описуємо ВІЗУАЛ в розмітці
// flex - display: flex
// flex-col - flex-direction: column
// p-4 - padding: 1rem (16px)
// bg-white - background-color: white
// rounded-lg - border-radius: 0.5rem
// hover:shadow-xl - box-shadow (...) ТІЛЬКИ на ховер
<div className="flex flex-col p-4 bg-white rounded-lg hover:shadow-xl">
  <h2 className="text-xl font-bold text-gray-900">Заголовок</h2>
</div>

Архітектура Tailwind: Tailwind — це не просто CSS файл на 10 мегабайт з усіма можливими класами. Це PostCSS плагін / компілятор. На етапі збірки парсер сканує всі ваші .jsx/.tsx файли, знаходить рядки з класами і генерує остаточний CSS-файл, у якому знаходяться ТІЛЬКИ ті класи, які ви реально використали. Результат: фінальний CSS файл зазвичай важить менше 10 Кілобайт (Блискавичне завантаження мережі).

Критика: “Це виглядає як інлайн-стилі!”. Відповідь інженерів: Ні. Інлайн-стилі (style=) не підтримують медіа-запити (Responsive design, класи типу md:flex), псевдокласи (hover:), і вони затягують рендеринг DOM, бо парсяться рушієм на льоту. Tailwind — це статично згенерований CSS.


5. Дизайн-системи та Бібліотеки компонентів

Коли компанія створює Enterprise CRM-систему на 500 сторінок, інженери не витрачають тижні на створення власного компонента “Календар (Datepicker)” або “Багатозначний Select з пошуком”. Вони використовують готові дизайн-системи.

  1. Material UI (MUI): Найстаріша і найпопулярніша екосистема, заснована на Material Design від Google. Містить сотні “важких” компонентів, ідеально підходить для швидкого старту B2B/Адмін панелей. Мінус: Додатки часто виглядають “однаково” і перевизначення стилів (Customization) MUI буває дуже складним.
  2. Ant Design: Китайський аналог MUI, надзвичайно потужний в контексті складних таблиць (Data Grids) і форм.
  3. Chakra UI / Mantine: Сучасні бібліотеки. Замість важкого об’єктного підходу MUI, вони максимально гнучкі і використовують пропси для стилізації (напр. <Box p={4} bg="red">), що нагадує Tailwind, але в форматі JavaScript компонентів.

Ці бібліотеки дають розробнику компоненти, які “працюють з коробки” і “виглядають добре з коробки”.


6. Headless UI Бібліотеки (Новий інженерний стандарт)

Найсучасніший і найцікавіший підхід, який домінує в Senior-командах сьогодні: Headless UI (Безголові інтерфейси). Найбільший архітектурний біль традиційних бібліотек (типу MUI/Bootstrap) полягає в тому, що їхня Логіка (відкриття/закриття модалки на клік поза нею, управління фокусом з клавіатури, ARIA-атрибути для скрінрідерів незрячих людей) ЖОРСТКО ЗВ’ЯЗАНА з їхнім Зовнішнім виглядом (колір кнопки, тінь модалки).

Headless UI (напр., Radix UI, Headless UI, Headless React Table) абстрагує це: Вони надають вам 100% готову ВНУТРІШНЮ логіку (навігація стрілками клавіатури по списку випадаючого меню, сувора відповідність стандартам доступності WAI-ARIA), але вони зовсім не містять CSS стилів (0 рядків зовнішнього вигляду).

// Приклад Radix UI. Бібліотека бере на себе керування фокусом, escape-клавішею
// А візуал (className) ми повністю накидаємо свій (через Tailwind або CSS Modules).
import * as Dialog from "@radix-ui/react-dialog";

export default () => (
  <Dialog.Root>
    <Dialog.Trigger className="bg-blue-500 rounded p-2">
      Відкрити
    </Dialog.Trigger>

    <Dialog.Portal>
      <Dialog.Overlay className="bg-black/50 fixed inset-0" />
      <Dialog.Content className="bg-white p-6 rounded-xl fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
        <Dialog.Title>Безголова Модалка</Dialog.Title>
        <Dialog.Description>
          Дизайн повністю мій, а керування доступністю - від Radix.
        </Dialog.Description>
        <Dialog.Close className="absolute top-2 right-2">X</Dialog.Close>
      </Dialog.Content>
    </Dialog.Portal>
  </Dialog.Root>
);

Комбінація Headless UI + Tailwind CSS (або бібліотека shadcn/ui, яка базується на цьому принципі) є сучасним еталоном побудови власних дизайн-систем у 2024+ роках.


Висновки

  1. Проблема глобального області видимості (Global Scope) у класичному CSS остаточно вирішується за допомогою різних парадигм інкапсуляції в компонентному світі React.
  2. CSS Modules є найбезпечнішим нативним переходом від глобального CSS до локального шляхом “на льоту” генерації хешованих імен класів через бандлери.
  3. CSS-in-JS підхід (Styled Components) надає потужну функціональність завдяки інтерполяції JS-змінних (Пропсів) прямо в CSS, проте вносить відчутний вплив (overhead) на Runtime (швидкість завантаження) на великих сторінках і непідтримуваний в умовах виключно серверного рендерингу.
  4. Utility-first CSS (Tailwind) кардинально прискорює “швидкість доставки” UI-компонентів розробником та генерує мінімальний розмір CSS-бандлу (через Purge/JIT системи компіляції), переносячи відповідальність за дизайн у розмітку JSX.
  5. Готові Component Libraries (Material UI) підходять для швидкого B2B/B2C релізу без кастомного дизайну, тоді як інноваційні Headless UI бібліотеки (Radix) пропонують ідеальну Інкапсуляцію: постачання складної системної логіки та A11Y-доступності без жодного нав’язаного пікселя дизайну.

Джерела

  1. “CSS Modules Documentation”. Github. URL: https://github.com/css-modules/css-modules
  2. Styled Components Official Docs. URL: https://styled-components.com/
  3. Tailwind CSS Official Documentation (Розділ: Core Concepts / Utility-First). URL: https://tailwindcss.com/docs/utility-first
  4. Material UI (MUI) React Framework. URL: https://mui.com/
  5. “Headless UI: The future of component libraries”. Blog Post.
  6. Radix UI Primitives (Unstyled accessible React components). URL: https://www.radix-ui.com/
  7. Shadcn/ui (UI компоненти на базі Tailwind CSS та Radix UI). URL: https://ui.shadcn.com/

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

  1. Чому імпорт двох звичайних файлів .css з однаковим класом .header у різні незалежні React-компоненти спричинить візуальну поломку на екрані, і як парадигма CSS Modules архітектурно вирішує цей конфлікт специфічності (Specificity)?
  2. Проаналізуйте недоліки підходу CSS-in-JS (Styled Components) у контексті продуктивності в Runtime (під час виконання). Чому створення CSS-класів за допомогою JavaScript прямо в браузері є “важчою” операцією, ніж отримання готового статичного .css файлу?
  3. Поясніть концепцію “атомарного класу” у парадигмі Utility-first (Tailwind CSS). Як JIT-компілятор (Just-In-Time) Tailwind обробляє тисячі можливих класів, щоб на виході видати CSS-файл розміром лише 10-15 кілобайт?
  4. У чому полягає філософська та архітектурна різниця між “Важкими” бібліотеками компонентів (Material UI, Ant Design) і так званими “Безголовими” бібліотеками (Radix UI, Headless UI)?
  5. Що таке WAI-ARIA (Accessibility) і чому написання звичайного <div onClick={close}>X</div> замість правильно скомпонованої модалки порушує інженерні критерії європейського та американського законодавства про доступність веб-контенту? Як Headless UI допомагає розробнику з цією проблемою?