Пошук уроків, статей та іншого контенту
Спроєктуємо структуру папок і файлів CSS для компонентів, утиліт, тем і базових стилів.
У невеликому проєкті один файл style.css може бути зручним. Але зі зростанням проєкту в ньому швидко змішуються:
базові стилі браузера;
типографіка;
стилі компонентів;
службові класи;
змінні кольорів і тем;
тимчасові виправлення.
Через це складно зрозуміти:
де шукати стилі конкретного компонента;
які правила є глобальними;
що можна безпечно змінити;
чому одне правило перевизначає інше;
які файли потрібно підключити для нової сторінки.
Організація CSS за відповідальністю допомагає розділити код на передбачувані частини.
Один із практичних варіантів структури:
project/
├── index.html
└── styles/
├── main.css
├── base/
│ ├── reset.css
│ ├── base.css
│ └── typography.css
├── components/
│ ├── button.css
│ ├── card.css
│ └── header.css
├── utilities/
│ ├── spacing.css
│ └── visibility.css
└── themes/
├── light.css
└── dark.cssКожна папка має окрему відповідальність:
main.css — єдина точка входу в стилі;
base/ — глобальні та базові правила;
components/ — стилі окремих інтерфейсних компонентів;
utilities/ — маленькі класи для одноразових або службових змін;
themes/ — змінні та правила різних тем.
Назви файлів мають описувати їхній вміст. Наприклад, button.css зрозуміліший за common.css, якщо файл містить стилі кнопок.
main.cssHTML підключає лише один CSS-файл:
<link rel="stylesheet" href="./styles/main.css">Файл main.css підключає інші частини в узгодженому порядку:
@import url("./base/reset.css");
@import url("./base/base.css");
@import url("./base/typography.css");
@import url("./themes/light.css");
@import url("./themes/dark.css");
@import url("./components/button.css");
@import url("./components/card.css");
@import url("./components/header.css");
@import url("./utilities/spacing.css");
@import url("./utilities/visibility.css");Порядок важливий:
скидання браузерних стилів;
глобальні правила;
типографіка;
змінні тем;
компоненти;
утиліти.
@import потрібно розміщувати на початку CSS-файлу. Після нього не слід додавати звичайні правила, а потім знову використовувати @import.
@importзручний для навчальних і невеликих проєктів. У великих проєктах CSS часто об’єднується у фінальний файл засобами збірки, але логічний поділ файлів залишається таким самим.
reset.cssЦей файл прибирає непередбачувані стандартні відступи та задає зрозумілу модель розрахунку розмірів.
/* Базове скидання стилів браузера */
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
line-height: 1.5;
}
body,
h1,
h2,
h3,
p,
ul {
margin: 0;
}
ul {
padding: 0;
list-style: none;
}
button,
input {
font: inherit;
}Скидання має бути невеликим і передбачуваним. Не потрібно без потреби обнуляти всі можливі стилі HTML-елементів.
base.cssУ цьому файлі розміщують стилі документа загалом: фон, колір тексту, ширину контейнера, базові властивості сторінки.
/* Глобальні стилі сторінки */
:root {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: var(--color-text);
background-color: var(--color-background);
}
body {
min-width: 320px;
min-height: 100vh;
background-color: var(--color-background);
color: var(--color-text);
}
.container {
width: min(100% - 32px, 1120px);
margin-inline: auto;
}Клас .container є глобальним макетним класом. Він не належить конкретному компоненту, тому його доречно зберігати в base/.
typography.cssТипографічні правила також можна винести в окремий файл:
/* Базова типографіка */
h1,
h2,
h3 {
color: var(--color-heading);
line-height: 1.2;
}
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
}
h2 {
font-size: clamp(1.5rem, 3vw, 2.25rem);
}
p {
max-width: 70ch;
}Тут не повинно бути стилів конкретної картки або кнопки. Файл відповідає лише за загальні текстові правила.
Значення, які можуть змінюватися між темами, зручно зберігати у CSS-змінних.
Файл themes/light.css:
/* Значення світлої теми */
:root {
color-scheme: light;
--color-background: #f5f7fb;
--color-surface: #ffffff;
--color-text: #243044;
--color-heading: #111827;
--color-muted: #667085;
--color-border: #d0d5dd;
--color-primary: #2563eb;
--color-primary-contrast: #ffffff;
--radius-md: 12px;
--shadow-card: 0 8px 24px rgb(15 23 42 / 10%);
}Файл themes/dark.css:
/* Значення темної теми */
[data-theme="dark"] {
color-scheme: dark;
--color-background: #111827;
--color-surface: #1f2937;
--color-text: #d1d5db;
--color-heading: #f9fafb;
--color-muted: #9ca3af;
--color-border: #374151;
--color-primary: #60a5fa;
--color-primary-contrast: #111827;
--shadow-card: 0 8px 24px rgb(0 0 0 / 25%);
}Компоненти використовують змінні, а не конкретні значення кольорів:
.card {
background-color: var(--color-surface);
color: var(--color-text);
box-shadow: var(--shadow-card);
}Завдяки цьому компонент не потрібно переписувати для кожної теми.
Тему можна вказати на кореневому елементі документа:
<html lang="uk" data-theme="dark">Для світлої теми атрибут можна не вказувати або встановити значення light:
<html lang="uk" data-theme="light">Селектор [data-theme="dark"] у dark.css застосовується до всіх елементів усередині документа з такою ознакою на html.
Компонентний файл має містити правила одного компонента або невеликої групи тісно пов’язаних елементів.
button.css/* Компонент кнопки */
.button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
padding: 10px 18px;
border: 1px solid transparent;
border-radius: 8px;
cursor: pointer;
font-weight: 700;
transition:
background-color 160ms ease,
border-color 160ms ease,
transform 160ms ease;
}
.button:hover {
transform: translateY(-1px);
}
.button:focus-visible {
outline: 3px solid rgb(37 99 235 / 35%);
outline-offset: 3px;
}
.button--primary {
background-color: var(--color-primary);
color: var(--color-primary-contrast);
}
.button--primary:hover {
background-color: color-mix(in srgb, var(--color-primary) 85%, black);
}
.button--secondary {
border-color: var(--color-border);
background-color: transparent;
color: var(--color-text);
}У цьому прикладі:
.button — базовий стиль компонента;
.button--primary і .button--secondary — його варіанти;
стилі кнопки не змішані зі стилями картки або заголовка сторінки.
Для позначення варіантів використано схему, подібну до BEM: подвійне тире вказує на модифікатор.
card.css/* Компонент картки */
.card {
padding: 24px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background-color: var(--color-surface);
box-shadow: var(--shadow-card);
}
.card__title {
margin-bottom: 8px;
font-size: 1.25rem;
}
.card__description {
margin-bottom: 20px;
color: var(--color-muted);
}Класи з подвійним підкресленням, наприклад .card__title, позначають частини компонента. Їхні назви дають змогу зрозуміти зв’язок між елементом і батьківським компонентом.
header.css/* Компонент верхньої панелі */
.site-header {
padding-block: 24px;
border-bottom: 1px solid var(--color-border);
}
.site-header__content {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.site-header__title {
color: var(--color-heading);
font-size: 1.25rem;
font-weight: 700;
}Компонент не повинен залежати від конкретного місця на сторінці. Наприклад, .site-header має працювати і на головній сторінці, і на сторінці налаштувань.
Утиліта — це маленький клас з однією чіткою функцією. Вона не описує складний компонент.
spacing.css/* Утиліти для вертикальних відступів */
.u-stack-sm > * + * {
margin-top: 8px;
}
.u-stack-md > * + * {
margin-top: 16px;
}
.u-stack-lg > * + * {
margin-top: 24px;
}Селектор > * + * додає відступ лише між сусідніми дочірніми елементами, але не перед першим елементом.
visibility.css/* Утиліти для керування видимістю */
.u-hidden {
display: none;
}
.u-visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}.u-hidden повністю прибирає елемент із розкладки.
.u-visually-hidden приховує елемент візуально, але залишає його доступним для допоміжних технологій. Такий клас часто використовують для прихованих підписів форм або заголовків.
Утиліти потрібно використовувати помірно. Якщо для одного елемента потрібні численні утиліти, це може бути ознакою, що варто створити окремий компонент.
Структура:
project/
├── index.html
└── styles/
├── main.css
├── base/
│ ├── reset.css
│ ├── base.css
│ └── typography.css
├── components/
│ ├── button.css
│ ├── card.css
│ └── header.css
├── utilities/
│ ├── spacing.css
│ └── visibility.css
└── themes/
├── light.css
└── dark.cssФайл index.html:
<!doctype html>
<html lang="uk" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Структурований CSS</title>
<link rel="stylesheet" href="./styles/main.css">
</head>
<body>
<header class="site-header">
<div class="container site-header__content">
<p class="site-header__title">CSS Architecture</p>
<button class="button button--secondary" type="button">
Увійти
</button>
</div>
</header>
<main class="container u-stack-lg">
<section class="u-stack-sm">
<h1>Структура стилів</h1>
<p>
Кожна частина CSS відповідає за окремий рівень інтерфейсу.
</p>
</section>
<article class="card u-stack-md">
<div>
<h2 class="card__title">Компонентна картка</h2>
<p class="card__description">
Стилі картки зберігаються окремо від глобальних правил.
</p>
</div>
<button class="button button--primary" type="button">
Переглянути
</button>
</article>
</main>
</body>
</html>Щоб перевірити темну тему, змініть у цьому файлі:
<html lang="uk" data-theme="dark">Після цього компоненти використають ті самі правила, але отримають інші значення CSS-змінних.
Перед додаванням правила поставте собі кілька запитань.
Наприклад:
box-sizing;
базовий шрифт;
колір сторінки;
загальні стилі заголовків.
Такі правила належать до base/.
Наприклад:
кнопка;
картка;
навігаційна панель;
модальне вікно.
Для нього варто створити файл у components/.
Наприклад:
приховати елемент;
додати вертикальний проміжок;
вирівняти текст;
обмежити ширину.
Таке правило може бути утилітою в utilities/.
Наприклад:
колір тла;
колір тексту;
колір рамки;
тінь.
Значення потрібно винести в змінні файлів із themes/.
Проблеми часто виникають через надто загальні селектори:
/* Надто загальний селектор */
.title {
margin-bottom: 20px;
}Клас .title може випадково використовуватися в різних частинах інтерфейсу. Краще пов’язати елемент із компонентом:
.card__title {
margin-bottom: 8px;
}
.site-header__title {
margin-bottom: 0;
}Також варто обережно використовувати селектори за тегами:
/* Це правило вплине на всі кнопки */
button {
border: 0;
}Якщо правило потрібне лише компоненту, обмежте його класом:
.button {
border: 0;
}Глобальні селектори доречні для справді глобальних правил. Компонентні стилі краще прив’язувати до класів компонентів.
Каскад CSS враховує, зокрема:
специфічність селектора;
порядок правил у файлах;
порядок підключення файлів;
стан елемента, наприклад :hover або :focus-visible.
Через це утиліти часто підключають після компонентів. Тоді просте службове правило може змінити властивість компонента без складного селектора.
Наприклад:
.card {
display: block;
}
.u-hidden {
display: none;
}Якщо .u-hidden підключено після .card і має таку саму специфічність, елемент буде прихований.
Не варто вирішувати конфлікти за допомогою постійного підвищення специфічності:
/* Не бажано */
main .container article.card .card__title {
color: red;
}Краще:
зменшити область дії селектора;
виправити порядок підключення;
винести варіант компонента в окремий модифікатор;
не використовувати !important без необхідності.
Файл на кілька тисяч рядків складно підтримувати. Розділяйте стилі за відповідальністю, а не просто за розміром.
common.css без чіткої роліНазва common.css не пояснює, що саме міститься всередині. Краще використовувати конкретні назви: reset.css, typography.css, button.css, spacing.css.
Якщо один і той самий колір записаний у десятках місць, зміну теми буде складно реалізувати. Спільні значення потрібно зберігати у CSS-змінних.
Складні стилі картки не варто розкладати на десятки службових класів. Компонент має описувати власний вигляд, а утиліта — одну незалежну функцію.
Класи .title, .text або .box можуть мати різний зміст у різних частинах сторінки. Використовуйте назви, що описують компонент і його роль.
Якщо файли підключаються у випадковому порядку, зміни стають непередбачуваними. Зафіксуйте порядок у main.css і дотримуйтеся його.
!important!important приховує проблему каскаду, але не вирішує її архітектурно. Спочатку перевірте специфічність і порядок правил.
Використовуйте main.css як єдину точку входу.
Розділяйте CSS на base, components, utilities і themes.
У базових стилях зберігайте глобальні правила, а не стилі конкретних компонентів.
Кожен компонент краще зберігати в окремому файлі.
Утиліта має виконувати одну невелику функцію.
Значення, що залежать від теми, зберігайте в CSS-змінних.
Заздалегідь визначений порядок підключення файлів спрощує контроль каскаду.
Уникайте надто загальних селекторів, дублювання та постійного використання !important.