Протягом понад десяти років Sass/SCSS був негласним стандартом у CSS-розробці. Причина проста: нативний CSS не мав вкладення правил, не мав пріоритетних шарів, не міг анімувати кастомні властивості, не мав засобів ізоляції стилів. Sass заповнював ці прогалини.
З 2023–2024 років ситуація кардинально змінилась. CSS отримав нативне вкладення (Nesting), каскадні шари (@layer), звуження стилів (@scope) та типізовані Custom Properties (@property). Для більшості проєктів ці чотири специфікації замінюють те, навіщо раніше потрібен був препроцесор.
Ця стаття — практичне порівняння: що ви робили у SCSS, і як це виглядає тепер у нативному CSS.
У класичному CSS стилізація компонента з кількома станами виглядала так:
/* Класичний CSS — багато повторень */
.card { background: white; border-radius: 8px; }
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.card.active { border-color: #6366f1; }
.card__title { font-size: 1.25rem; color: #1e293b; }
.card__title:hover { color: #6366f1; }
.card__body { padding: 1rem; color: #64748b; }
.card__footer { border-top: 1px solid #e2e8f0; padding: 0.75rem; }
Щоразу потрібно повторювати .card, що важко підтримувати при перейменуванні. SCSS вирішував це через вкладення — і тепер це вміє нативний CSS.
Підтримка: Chrome 112+, Firefox 117+, Safari 17+
/* Native CSS Nesting */
.card {
background: white;
border-radius: 8px;
/* Вкладений псевдоклас */
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* Вкладена модифікація класу */
&.active {
border: 2px solid #6366f1;
}
/* Вкладений дочірній елемент (BEM) */
& .card__title {
font-size: 1.25rem;
color: #1e293b;
/* Ще глибше вкладення */
&:hover {
color: #6366f1;
}
}
/* Вкладений псевдоелемент */
&::before {
content: '';
display: block;
}
}
Символ & — це посилання на батьківський селектор. Без нього CSS не знає, до якого елемента застосовувати вкладене правило.
<div class="nesting-demo">
<div class="n-card">
<div class="n-card__badge">Нове</div>
<div class="n-card__title">CSS Nesting</div>
<div class="n-card__body">Наведіть на картку, щоб побачити hover-стан. Нативне вкладення без SCSS.</div>
<div class="n-card__footer">
<button class="n-btn n-btn--primary">Дізнатись більше</button>
<button class="n-btn n-btn--ghost">Пізніше</button>
</div>
</div>
<div class="n-card n-card--active">
<div class="n-card__badge n-card__badge--active">Активна</div>
<div class="n-card__title">Стан .active</div>
<div class="n-card__body">Ця картка має клас <code>.n-card--active</code> — стилізована через вкладений &.active.</div>
<div class="n-card__footer">
<button class="n-btn n-btn--primary">Відкрити</button>
</div>
</div>
</div>
.nesting-demo {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
padding: 1rem;
background: #f1f5f9;
font-family: system-ui, sans-serif;
}
.n-card {
flex: 1;
min-width: 200px;
background: white;
border-radius: 10px;
border: 1.5px solid #e2e8f0;
overflow: hidden;
transition: box-shadow 0.2s, border-color 0.2s;
cursor: pointer;
&:hover {
box-shadow: 0 8px 24px rgba(99, 102, 241, 0.12);
border-color: #c7d2fe;
}
&--active {
border-color: #6366f1;
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.15);
}
&__badge {
display: inline-block;
margin: 0.75rem 0.75rem 0;
padding: 0.2rem 0.6rem;
background: #ede9fe;
color: #6366f1;
border-radius: 100px;
font-size: 0.72rem;
font-weight: 700;
&--active {
background: #6366f1;
color: white;
}
}
&__title {
padding: 0.4rem 0.75rem 0;
font-size: 1rem;
font-weight: 700;
color: #1e293b;
}
&__body {
padding: 0.4rem 0.75rem;
font-size: 0.82rem;
color: #64748b;
line-height: 1.5;
& code {
background: #f1f5f9;
padding: 0.1em 0.3em;
border-radius: 3px;
font-size: 0.9em;
}
}
&__footer {
padding: 0.6rem 0.75rem;
border-top: 1px solid #f1f5f9;
display: flex;
gap: 0.5rem;
}
}
.n-btn {
padding: 0.35rem 0.85rem;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
border: 1.5px solid;
font-family: inherit;
transition: all 0.15s;
&--primary {
background: #6366f1;
color: white;
border-color: #6366f1;
&:hover { background: #4f46e5; border-color: #4f46e5; }
}
&--ghost {
background: transparent;
color: #64748b;
border-color: #e2e8f0;
&:hover { background: #f8fafc; color: #1e293b; }
}
}
CSS Nesting дозволяє вкладати @media безпосередньо всередині правила:
.hero {
padding: 2rem;
font-size: 1rem;
/* Замість окремого @media { .hero { } } */
@media (min-width: 768px) {
padding: 4rem;
font-size: 1.25rem;
}
@media (min-width: 1200px) {
padding: 6rem;
font-size: 1.5rem;
}
}
<div class="media-nesting-demo">
<div class="responsive-hero">
<h2>Адаптивний заголовок</h2>
<p>Padding та font-size змінюються через <code>@media</code> вкладений у правило компонента. Змініть ширину вікна.</p>
</div>
<div class="responsive-grid">
<div class="rg-item">A</div>
<div class="rg-item">B</div>
<div class="rg-item">C</div>
<div class="rg-item">D</div>
</div>
</div>
.media-nesting-demo {
font-family: system-ui, sans-serif;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 0.5rem;
background: #f8fafc;
}
.responsive-hero {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: white;
border-radius: 10px;
padding: 1rem;
font-size: 0.85rem;
@media (min-width: 500px) {
padding: 2rem;
font-size: 1rem;
}
& h2 { margin: 0 0 0.5rem; font-size: 1.2em; }
& p { margin: 0; opacity: 0.85; line-height: 1.5; }
& code { background: rgba(255,255,255,0.2); padding: 0.1em 0.3em; border-radius: 3px; }
}
.responsive-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
@media (min-width: 500px) {
grid-template-columns: repeat(4, 1fr);
}
}
.rg-item {
background: #6366f1;
color: white;
border-radius: 6px;
padding: 1rem;
text-align: center;
font-weight: 800;
font-size: 1.2rem;
}
// Без компілятора — не працює в браузері
.button {
padding: 0.6em 1.2em;
background: $primary;
border-radius: 6px;
transition: all 0.15s;
&:hover {
background: darken($primary, 10%);
}
&:focus-visible {
outline: 2px solid $primary;
outline-offset: 2px;
}
&--large {
padding: 0.8em 1.6em;
font-size: 1.125rem;
}
&--ghost {
background: transparent;
border: 2px solid $primary;
color: $primary;
&:hover {
background: rgba($primary, 0.1);
}
}
@include respond-to('md') {
padding: 0.75em 1.5em;
}
}
/* Пряму в браузері — без компілятора */
.button {
padding: 0.6em 1.2em;
background: var(--primary, #6366f1);
border-radius: 6px;
transition: all 0.15s;
&:hover {
background: color-mix(in srgb, var(--primary) 85%, black);
}
&:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
&--large {
padding: 0.8em 1.6em;
font-size: 1.125rem;
}
&--ghost {
background: transparent;
border: 2px solid var(--primary);
color: var(--primary);
&:hover {
background: color-mix(in srgb, var(--primary) 10%, transparent);
}
}
@media (min-width: 768px) {
padding: 0.75em 1.5em;
}
}
darken($color, 10%) із Sass тепер замінює color-mix(in srgb, var(--color) 85%, black) — нативна CSS-функція, що стала доступною у 2023 році. Детальніше — у статті про сучасні можливості CSS.@layer — Каскадні шари (Cascade Layers)Уявіть: ви підключаєте бібліотеку компонентів. Вона задає .button { background: blue } з певною специфічністю. Ваш код задає .button { background: green }. Хто переможе? Залежить від порядку підключення та специфічності — і це часто непередбачувано.
Ця проблема існувала роками: CSS-in-JS, BEM, CSS Modules — всі вони по-різному намагались її вирішити. @layer дає нативне рішення: явно оголошений пріоритет шарів.
Підтримка: Chrome 99+, Firefox 97+, Safari 15.4+
/* Крок 1: оголосити порядок шарів (від найнижчого до найвищого пріоритету) */
@layer base, components, utilities;
/* Крок 2: наповнити шари */
@layer base {
/* Базові стилі, CSS Reset */
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
h1, h2, h3 { line-height: 1.2; }
}
@layer components {
/* Стилі компонентів */
.button {
padding: 0.6em 1.2em;
background: #6366f1;
color: white;
border-radius: 6px;
border: none;
cursor: pointer;
}
}
@layer utilities {
/* Утиліти мають найвищий пріоритет серед шарів */
.hidden { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
}
/* Стилі поза @layer мають НАЙВИЩИЙ пріоритет — вище всіх шарів */
.button {
background: #ec4899; /* Переможе компонент у @layer */
}
<div class="layer-demo">
<p class="layer-explain">
Пріоритет: <strong>без шару</strong> > utilities > components > base
</p>
<div class="layer-boxes">
<div class="layer-box lb-base">
@layer base<br><small>найнижчий пріоритет</small>
</div>
<div class="layer-box lb-components">
@layer components<br><small>середній</small>
</div>
<div class="layer-box lb-utilities">
@layer utilities<br><small>вищий</small>
</div>
<div class="layer-box lb-unlayered">
без @layer<br><small>найвищий!</small>
</div>
</div>
<div class="conflict-demo">
<p class="conflict-label">Демо: всі правила задають <code>background</code>, перемагає вищий пріоритет</p>
<div class="conflict-btn">Ця кнопка</div>
</div>
</div>
.layer-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.layer-explain { margin: 0; color: #64748b; }
.layer-explain strong { color: #1e293b; }
.layer-boxes { display: flex; gap: 4px; flex-wrap: wrap; }
.layer-box {
flex: 1;
min-width: 80px;
padding: 0.6rem;
border-radius: 6px;
font-size: 0.78rem;
font-weight: 700;
text-align: center;
color: white;
line-height: 1.3;
}
.layer-box small { display: block; font-weight: 400; font-size: 0.7rem; opacity: 0.85; }
.lb-base { background: #94a3b8; }
.lb-components { background: #6366f1; }
.lb-utilities { background: #8b5cf6; }
.lb-unlayered { background: #ec4899; }
@layer ld-base {
.conflict-btn { background: #94a3b8; }
}
@layer ld-components {
.conflict-btn { background: #6366f1; }
}
@layer ld-utilities {
.conflict-btn { background: #8b5cf6; }
}
/* Без шару — найвищий пріоритет */
.conflict-btn { background: #ec4899; }
.conflict-demo {
background: white;
border-radius: 8px;
border: 1px solid #e2e8f0;
padding: 0.75rem;
}
.conflict-label { margin: 0 0 0.5rem; color: #64748b; font-size: 0.78rem; }
.conflict-label code { background: #f1f5f9; padding: 0.1em 0.3em; border-radius: 3px; }
.conflict-btn {
display: inline-block;
padding: 0.5rem 1.25rem;
color: white;
border-radius: 6px;
font-weight: 700;
font-size: 0.85rem;
}
Найпрактичніший сценарій — ізолювати стилі бібліотеки у шарі з найнижчим пріоритетом:
/* Ваші власні шари — оголошені першими */
@layer reset, third-party, base, components, utilities;
/* Стилі бібліотеки — потрапляють у шар з низьким пріоритетом */
@import url('./normalize.css') layer(reset);
@import url('./bootstrap.css') layer(third-party);
/* Ваші компоненти ЗАВЖДИ перевизначать бібліотеку */
@layer components {
.button {
/* Ці стилі матимуть вищий пріоритет за Bootstrap .button,
навіть без !important та без підвищення специфічності */
background: var(--brand);
}
}
@layer, мають пріоритет вище будь-якого шару незалежно від специфічності. Це дозволяє зберегти «аварійний вихід» для критичних перевизначень.@scope — Обмеження стилівCSS-селектори глобальні за замовчуванням. .title у кухонному таймері може вплинути на .title у модальному вікні, навіть якщо вони далеко у DOM. Це проблема, яку вирішують CSS Modules, Shadow DOM, BEM-конвенції. @scope — нативне вирішення.
Підтримка: Chrome 118+, Safari 17.4+, Firefox (у розробці)
/* Стилі активні ЛИШЕ всередині .card */
@scope (.card) {
/* Тут можна писати короткі селектори без боязні конфліктів */
.title {
font-size: 1.25rem;
color: #1e293b;
}
.body {
color: #64748b;
}
/* Виключення: стилі НЕ торкнуться .nested-card та його нащадків */
@scope (.card) to (.nested-card) {
.title { color: #6366f1; }
}
}
<div class="scope-demo">
<p class="scope-note">@scope (.product-card) — стилі .title та .price діють лише всередині</p>
<div class="scope-layout">
<div class="product-card">
<div class="title">AirPods Pro</div>
<div class="price">$249</div>
<div class="desc">Активне шумозаглушення</div>
</div>
<div class="article-card">
<div class="title">Стаття: CSS у 2024</div>
<div class="price">Безкоштовно</div>
<div class="desc">Нові можливості мови</div>
</div>
</div>
<p class="scope-note" style="margin-top: 0.5rem">
Обидва компоненти мають <code>.title</code> та <code>.price</code> — стилі не конфліктують завдяки @scope
</p>
</div>
.scope-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
}
.scope-note { margin: 0 0 0.5rem; color: #64748b; font-size: 0.78rem; }
.scope-note code { background: #f1f5f9; padding: 0.1em 0.3em; border-radius: 3px; }
.scope-layout { display: flex; gap: 0.75rem; flex-wrap: wrap; }
/* Стилі картки продукту — ізольовані через @scope */
@scope (.product-card) {
:scope {
background: white;
border-radius: 10px;
border: 1.5px solid #e2e8f0;
padding: 1rem;
flex: 1;
min-width: 140px;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.title {
font-size: 1rem;
font-weight: 700;
color: #1e293b;
}
.price {
font-size: 1.2rem;
font-weight: 800;
color: #6366f1;
}
.desc {
font-size: 0.78rem;
color: #64748b;
}
}
/* Стилі статті — інші, не конфліктують */
@scope (.article-card) {
:scope {
background: #fef3c7;
border-radius: 10px;
border: 1.5px solid #fcd34d;
padding: 1rem;
flex: 1;
min-width: 140px;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.title {
font-size: 0.95rem;
font-weight: 700;
color: #92400e;
}
.price {
font-size: 0.85rem;
font-weight: 700;
color: #d97706;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.desc {
font-size: 0.78rem;
color: #b45309;
}
}
@property — Типізовані Custom PropertiesCSS Custom Properties (--color: #6366f1) — потужний інструмент, але вони мають обмеження: браузер не знає їх типу. Для нього --color — просто рядок. Тому transition: --color 0.3s не спрацює — браузер не знає, як інтерполювати «від рядка до рядка».
@property вирішує це, дозволяючи зареєструвати кастомну властивість із типом, початковим значенням та поведінкою наслідування.
Підтримка: Chrome 85+, Safari 16.4+, Firefox 128+
@property --hue {
syntax: '<number>'; /* тип: число */
inherits: false; /* не наслідується від батька */
initial-value: 0; /* початкове значення */
}
@property --brand-color {
syntax: '<color>';
inherits: true;
initial-value: #6366f1;
}
@property --progress {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
<div class="property-demo">
<p class="pd-label">Анімовані CSS Custom Properties через @property:</p>
<div class="animated-gradient">
<span>Gradient анімація через --hue</span>
</div>
<div class="progress-ring-wrap">
<div class="progress-ring">
<span>75%</span>
</div>
<p class="pd-hint">Progress ring через --progress custom property</p>
</div>
<div class="color-morph">
<span>Колір переливається через --brand-color</span>
</div>
</div>
@property --hue {
syntax: '<number>';
inherits: false;
initial-value: 240;
}
@property --progress {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
@property --morph-color {
syntax: '<color>';
inherits: false;
initial-value: #6366f1;
}
.property-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.pd-label { margin: 0; color: #64748b; font-style: italic; }
.pd-hint { margin: 0.25rem 0 0; font-size: 0.75rem; color: #64748b; }
/* Анімований градієнт через --hue */
.animated-gradient {
--hue: 240;
background: hsl(var(--hue), 80%, 60%);
border-radius: 8px;
padding: 1rem;
color: white;
font-weight: 700;
text-align: center;
animation: hue-rotate 4s linear infinite;
}
@keyframes hue-rotate {
to { --hue: 600; }
}
/* Progress ring */
.progress-ring-wrap {
display: flex;
align-items: center;
gap: 1rem;
}
.progress-ring {
--progress: 0%;
width: 80px;
height: 80px;
border-radius: 50%;
background: conic-gradient(
#6366f1 var(--progress),
#e2e8f0 var(--progress)
);
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 1rem;
color: #1e293b;
position: relative;
animation: fill-progress 2s ease-out forwards;
}
.progress-ring::before {
content: '';
position: absolute;
inset: 8px;
border-radius: 50%;
background: #f8fafc;
}
.progress-ring span {
position: relative;
z-index: 1;
}
@keyframes fill-progress {
to { --progress: 75%; }
}
/* Color morph */
.color-morph {
--morph-color: #6366f1;
background: var(--morph-color);
border-radius: 8px;
padding: 1rem;
color: white;
font-weight: 700;
text-align: center;
animation: color-shift 3s ease-in-out infinite alternate;
}
@keyframes color-shift {
to { --morph-color: #ec4899; }
}
Зверніть: animation: hue-rotate 4s linear infinite плавно анімує --hue від 240 до 600, а hsl(var(--hue), 80%, 60%) автоматично оновлює градієнт. Без @property такого не досягти — браузер не знав би, що --hue — це число, між яким можна інтерполювати.
@property для типізованих дефолтів/* Без @property: якщо --size не задано, результат непередбачуваний */
.box {
width: var(--size); /* undefined? '0'? */
}
/* З @property: завжди є initial-value */
@property --box-size {
syntax: '<length>';
inherits: false;
initial-value: 100px; /* гарантований fallback */
}
.box {
width: var(--box-size); /* завжди 100px, якщо не перевизначено */
}
/* Можна перевизначати як звичайно */
.box--large {
--box-size: 200px;
}
<div class="comparison-demo">
<p class="cd-title">Компонент кнопки: SCSS vs Native CSS</p>
<div class="btn-showcase">
<button class="nc-btn nc-btn--primary">Primary</button>
<button class="nc-btn nc-btn--secondary">Secondary</button>
<button class="nc-btn nc-btn--danger">Danger</button>
<button class="nc-btn nc-btn--primary" disabled>Disabled</button>
<button class="nc-btn nc-btn--primary nc-btn--lg">Large</button>
<button class="nc-btn nc-btn--primary nc-btn--sm">Small</button>
</div>
<p class="cd-note">Всі варіанти — через CSS Nesting + Custom Properties. Без SCSS.</p>
</div>
@property --btn-bg {
syntax: '<color>';
inherits: false;
initial-value: #6366f1;
}
@property --btn-color {
syntax: '<color>';
inherits: false;
initial-value: white;
}
.comparison-demo {
padding: 1rem;
background: #f8fafc;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: #1e293b;
}
.cd-title { margin: 0 0 0.75rem; font-weight: 700; color: #64748b; }
.cd-note { margin: 0.75rem 0 0; font-size: 0.75rem; color: #94a3b8; font-style: italic; }
.btn-showcase { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.nc-btn {
--btn-bg: #6366f1;
--btn-color: white;
--btn-border: transparent;
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5em 1.1em;
background: var(--btn-bg);
color: var(--btn-color);
border: 1.5px solid var(--btn-border);
border-radius: 7px;
font-size: 0.875rem;
font-weight: 600;
font-family: inherit;
cursor: pointer;
transition: all 0.15s;
&:hover:not(:disabled) {
background: color-mix(in srgb, var(--btn-bg) 85%, black);
}
&:focus-visible {
outline: 2px solid var(--btn-bg);
outline-offset: 2px;
}
&:disabled {
opacity: 0.45;
cursor: not-allowed;
}
&--primary {
--btn-bg: #6366f1;
--btn-color: white;
}
&--secondary {
--btn-bg: transparent;
--btn-color: #6366f1;
--btn-border: #6366f1;
&:hover:not(:disabled) {
--btn-bg: #ede9fe;
}
}
&--danger {
--btn-bg: #ef4444;
--btn-color: white;
}
&--lg {
padding: 0.7em 1.5em;
font-size: 1rem;
}
&--sm {
padding: 0.3em 0.75em;
font-size: 0.75rem;
}
}
Перепишіть цей SCSS у нативний CSS, зберігши всю функціональність:
.nav {
display: flex;
gap: 0.5rem;
padding: 0.75rem;
background: #1e293b;
&__link {
color: #94a3b8;
padding: 0.5rem 1rem;
border-radius: 6px;
text-decoration: none;
transition: all 0.15s;
&:hover { color: white; background: rgba(255,255,255,0.1); }
&.active { color: white; background: #6366f1; }
}
@include respond-to('sm') {
flex-direction: column;
}
}
Створіть CSS-систему для бібліотеки компонентів:
reset, tokens, components, overridesreset — скидання margin, padding, box-sizingtokens — змінні --color-primary, --spacing-base, --radiuscomponents — компонент .card з базовими стилямиoverrides — темна тема, що перевизначає кольори карткиПереконайтесь, що overrides перемагає components без !important.
Створіть картку товару з такими анімованими властивостями:
--card-elevation (тип <number>) — анімує box-shadow при hover--card-accent (тип <color>) — анімує колір акцентного бордера від #e2e8f0 до #6366f1 при hover--badge-progress (тип <percentage>) — запускає conic-gradient «прогрес» при завантаженні сторінкиВикористайте @property + CSS Nesting для компактного коду.
🔗 CSS Nesting
& посилається на батьківський селектор. Вкладайте псевдокласи, псевдоелементи, модифікатори та @media прямо у правило компонента. Chrome 112+, FF 117+, Safari 17+.📚 @layer
@layer base, components, utilities. Стилі вище у списку — нижчий пріоритет. Без шару — найвищий. Ізолюйте бібліотеки через @import ... layer(third-party).🔒 @scope
@scope (.card) { .title { } } — стилі діють лише всередині .card. Замінює BEM-префікси та CSS Modules для базових сценаріїв. Chrome 118+, Safari 17.4+.⚡ @property
syntax, inherits, initial-value. Дозволяє анімувати var(--color), var(--progress), var(--angle). Без @property — лише статичні значення.Сучасний CSS 2023–2025: Нові можливості
Огляд найсвіжіших CSS-специфікацій: :has() selector, CSS Anchor Positioning, Scroll-Driven Animations, @starting-style, color-mix(), light-dark(), View Transitions API, @scope, relative color syntax, і багато іншого.
CSS для форм та інтерактивних станів
Повне керівництво зі стилізації HTML-форм: псевдокласи :focus-visible, :user-valid, :has(), accent-color, кастомні checkbox/radio/select, floating label патерн, валідація без JavaScript та нативний CSS-only дизайн форм.