Огляд підходів до стилізації: 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-компонентів.
Якщо ви імпортуєте звичайний CSS файл у свій React-додаток:
import "./Button.css"; // Містить клас .title { color: red; }
import "./Header.css"; // Містить клас .title { color: blue; }
Збірник (Webpack/Vite) просто “склеїть” їх у єдиний <style> тег у секції <head>. Внаслідок каскадування (Cascading), останній імпортований клас перепише попередній, і всі елементи з класом title у всьому додатку стануть синіми. Ця проблема є критичною для Enterprise-рішень.
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).
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+).
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.
Коли компанія створює Enterprise CRM-систему на 500 сторінок, інженери не витрачають тижні на створення власного компонента “Календар (Datepicker)” або “Багатозначний Select з пошуком”. Вони використовують готові дизайн-системи.
<Box p={4} bg="red">), що нагадує Tailwind, але в форматі JavaScript компонентів.Ці бібліотеки дають розробнику компоненти, які “працюють з коробки” і “виглядають добре з коробки”.
Найсучасніший і найцікавіший підхід, який домінує в 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+ роках.
.css з однаковим класом .header у різні незалежні React-компоненти спричинить візуальну поломку на екрані, і як парадигма CSS Modules архітектурно вирішує цей конфлікт специфічності (Specificity)?.css файлу?<div onClick={close}>X</div> замість правильно скомпонованої модалки порушує інженерні критерії європейського та американського законодавства про доступність веб-контенту? Як Headless UI допомагає розробнику з цією проблемою?