🌐 Це неофіційний переклад статті CSS Wrapped 2025 від Chrome DevRel команди. Переклад зроблено для україномовної спільноти розробників. Оригінальний контент належить Google/Chrome.

CSS Wrapped 2025

CSS Wrapped 2025

Українською 🇺🇦

Цього року ми даємо вам нові інструменти для створення динамічного вебу. Створюйте динамічні інтерфейси, розширюйте свою уяву та грайтесь з цими потужними новими CSS-функціями.

Прокрутіть вниз, щоб дослідити

Ми створювали нові функції з думкою про вас. Готові побачити, що ми сформували у 2025 році? Chrome DevRel команда проведе вас через 22 CSS та UI функції, які з'явились на веб-платформі.

#🧩 Кастомізовані компоненти

Монстр-майстер працює над роботом у своїй майстерні

Цього року було спекотно в майстерні. Ми взяли десятилітню проблему стилізації випадаючих списків і довели її до досконалості. Також ми випустили нові базові блоки, такі як нативне anchor positioning та API для каруселей з прокруткою.

01

#Invoker Commands

Показуйте <dialog> модально (і не тільки) без JavaScript!

Щоб відкрити <dialog> модально, натиснувши на <button>, зазвичай потрібен обробник onclick, який викликає метод showModal на цьому діалозі.

<button onclick="document.querySelector('#my-dialog').showModal();">Показати діалог</button>
<dialog id="my-dialog">…</dialog>

З invoker commands — доступними з Chrome 135 — кнопки тепер можуть виконувати дії над іншими елементами декларативно, без потреби в JavaScript.

<button commandfor="my-dialog" command="show-modal">Показати діалог</button>
<dialog id="my-dialog">…</dialog>

Атрибут commandfor приймає ID — подібно до атрибута for — тоді якcommand приймає вбудовані значення, що забезпечує більш портативний та інтуїтивний підхід.

Наразі можна надсилати команди до [popover] та <dialog> елементів, з можливістю додавання інших типів елементів у майбутньому. Команди відповідають їхнім JavaScript-аналогам:

  • show-popover: el.showPopover()
  • hide-popover: el.hidePopover()
  • toggle-popover: el.togglePopover()
  • show-modal: dialogEl.showModal()
  • close: dialogEl.close()

Також можна налаштувати власні команди для надсилання елементам. Ці кастомні команди починаються з двох дефісів і обробляються подією toggle.

<button commandfor="some-element" command="--show-confetti">🎉</button>
document.querySelector('#some-element').addEventListener('command', (e) => {
if (e.command === "--show-confetti") {
// …
}
});

Доступний поліфіл для Invoker Commands.

02

#Dialog Light Dismiss

Приємна функція Popover API тепер доступна для <dialog>.

Одна з приємних функцій, представлених Popover API — це поведінка light dismiss для попаверів. Вона дозволяє користувачам закривати попавер, клікнувши поза ним — на ::backdrop — або натиснувши клавішу ESC.

З Chrome 134 ця поведінка light dismiss також доступна для <dialog>через новий атрибут closedby, який контролює поведінку:

  • <dialog closedby="none">: Жодного закриття діалогу користувачем. Це поведінка за замовчуванням.
  • <dialog closedby="closerequest">: Натискання ESC (або інший тригер закриття) закриває діалог
  • <dialog closedby="any">: Клік поза діалогом або натискання ESC закриває діалог. Подібно до поведінки popover="auto".
03

#popover="hint"

Ефемерні попавери, які не закривають інші.

Hint-попавери з popover="hint" — це новий тип HTML-попаверів, призначений для ефемерних шаруватих UI-патернів, таких як підказки або попередній перегляд посилань. Відкриття hint-попавера не закриває інші відкриті auto чи manual попавери, дозволяючи шаруватим UI-елементам співіснувати.

<button interestfor="callout-1"></button>
<div id="callout-1" popover=hint>
Інформація про продукт тут.
</div>

Використання popover="hint" разом з interest invokers (атрибут [interestfor]) значно спрощує створення шаруватих UI-елементів, таких як підказки, hover-картки та попередній перегляд декларативно в HTML та CSS, без складних JavaScript-обхідних шляхів.

Демо popover="hint" з interest invokers для відображення інформації про продукт.
04

#Customizable select

Нарешті можна стилізувати HTML select елементи за допомогою CSS.

Нарешті настав час: тепер ви можете повністю кастомізувати HTML <select> елемент за допомогою CSS!

Щоб почати, застосуйте властивість CSS appearance: base-select до вашого <select> елемента. Це переведе його в новий, мінімальний стан, оптимізований для кастомізації.

select {
&::picker(select) {
appearance: base-select;
}
}

Використання base-select відкриває кілька потужних функцій, включаючи повну CSS-кастомізацію. Кожна частина select-елемента, включаючи кнопку, випадаючий список та опції, може бути стилізована за допомогою CSS.

Діаграма customizable select з різними новими частинами

Новий select також дозволяє включати та правильно рендерити HTML-елементи, такі як <img>та <span>, прямо всередині <option> елементів.

Демо customizable select з іконками та багатим контентом всередині опцій.
05

#::scroll-marker/button()

Карусельні scroll-афорданси з нативними CSS псевдо-елементами.

Цього року створення каруселей та інших scroll-досвідів у CSS стало набагато простішим завдяки двом новим псевдо-елементам: ::scroll-button() та ::scroll-marker(). Ці функції дозволяють створювати нативні, доступні та продуктивні каруселі лише кількома рядками CSS, без потреби в JavaScript.

Карусель — це по суті область з прокруткою з доданими UI-афордансами для навігації: кнопками для прокрутки вперед-назад та маркерами для індикації поточної позиції.

Псевдо-елемент ::scroll-button() створює надані браузером, статусні та інтерактивні кнопки прокрутки. Ви можете створювати кнопки для будь-якого напрямку прокрутки:left, right, up, down, а також логічні напрямки як block-start та inline-end.

.carousel::scroll-button(left) {
content: "⬅" / "Прокрутити вліво";
}
.carousel::scroll-button(right) {
content: "⮕" / "Прокрутити вправо";
}

Псевдо-елемент ::scroll-marker представляє маркер для елемента всередині scroll-контейнера. Ці маркери групуються в ::scroll-marker-group і поводяться як anchor-посилання, дозволяючи користувачам переходити безпосередньо до конкретного елемента.

.carousel {
scroll-marker-group: after;
}
.carousel > li::scroll-marker {
content: ' ';
width: 1em;
height: 1em;
border: 1px solid black;
border-radius: 50%;
}
.carousel > li::scroll-marker:target-current {
background: black;
}
Карусель з кнопками прокрутки

::scroll-button()

Карусель з маркерами позиції

::scroll-marker

06

#scroll-target-group

Перетворіть список anchor-посилань на з'єднані scroll-маркери.

На додаток до псевдо-елементів ::scroll-button() та ::scroll-marker, CSS-каруселі включають ще одну класну функцію: scroll-target-group. Вона позначає елемент як контейнер для групи навігаційних елементів, наприклад, змісту.

Поєднайте scroll-target-group з псевдо-класом :target-current, щоб стилізувати anchor-елемент, ціль якого зараз видима. Це дає вам силу ::scroll-marker, але з гнучкістю використання власних HTML-елементів для маркерів.

<nav class="toc">
<ul>
<li><a href="#section-1">Секція 1</a></li>
<li><a href="#section-2">Секція 2</a></li>
<li><a href="#section-3">Секція 3</a></li>
</ul>
</nav>
<main>
<section id="section-1">...</section>
<section id="section-2">...</section>
<section id="section-3">...</section>
</main>
.toc {
scroll-target-group: auto;
}
.toc a:target-current {
color: red;
font-weight: bold;
}
07

#Anchored container queries

Стилізуйте елементи на основі їхньої anchor-позиції.

Минулорічний CSS Wrapped охоплював CSS anchor positioning: захоплююче оновлення, яке змінює спосіб позиціонування елементів відносно один одного. З того часу воно стало частиною Interop 2025, і підтримка браузерами розширилась.

Однак, хоча CSS міг переміщувати елемент до fallback-позиції, він не мав способу дізнатися, який fallback було обрано. Це означало, що якщо ваш tooltip перевертався знизу вгору екрана, стрілка все ще вказувала б у неправильному напрямку. Тепер це вирішено за допомогою anchored container queries.

/* Елемент, до якого прив'язаний tooltip */
.anchor {
anchor-name: --my-anchor;
}
/* Позиціонований елемент (tooltip) */
.tooltip {
position: fixed;
position-anchor: --my-anchor;
position-area: bottom;
position-try-fallbacks: flip-block;
/* Робимо його anchored query контейнером */
container-type: anchored;
}
/* Використовуємо anchored query для перевірки fallback */
@container anchored(fallback: flip-block) {
.tooltip::before {
/* Використано 'top' fallback, тому перевертаємо стрілку */
content: '▼';
top: 100%;
}
}

Це величезна перемога для anchor positioning та бібліотек компонентів, що дозволяє створювати більш надійні та самодостатні UI-елементи з меншим кодом.

Демо anchored container queries: стрілка tooltip автоматично перевертається при зміні позиції.
08

#Interest invokers

Декларативний UI, що активується інтересом, з interestfor.

UI, що активується наведенням та фокусом, є скрізь у вебі — від tooltips до багатих hover-карток та попереднього перегляду сторінок. Хоча цей патерн часто добре працює для користувачів миші, він може бути недоступним для інших модальностей, як-от сенсорний екран.

Новий атрибут interestfor вирішує цю проблему, надаючи нативний, декларативний спосіб стилізації елемента, коли користувачі "виявляють інтерес" до нього без повної активації.

<button interestfor="callout-1"></button>
<div id="callout-1" popover="hint">
Інформація про продукт тут.
</div>

Примітка: На відміну від command invokers, які працюють лише на кнопках, interest invokers можуть бути встановлені на посиланнях (<a>) так само як і на кнопках.

Демо interest invokers: наведення на кнопки показує інформацію про продукт.
Interest Delays

Ще одна нова функція — можливість встановлювати затримки інтересу. Це запобігає занадто передчасному спрацюванню interest-invoked елемента. Ви можете встановити затримку як для відкриття, так і для закриття за допомогою властивості interest-delay.

/* застосовує оновлене значення затримки на кнопку interest-invoking */
[interestfor] {
interest-delay: 0.2s;
}

#⚡ Взаємодії нового покоління

Футуристичний автомобіль на дорозі майбутнього

З цим новим інструментарієм взаємодій ви тепер можете анімувати переходи між сторінками за допомогою view transitions та створювати чудові scroll-based досвіди.

09

#Scroll-state queries

Стилізуйте нащадків залежно від того, чи є щось scrollable, stuck або snapped.

Щоб визначити, чи елемент stuck, snapped або scrollable, раніше потрібно було використовувати купу JavaScript... що не завжди легко зробити, бо треба прикріплювати таймаути до scroll-подій тощо.

Завдяки scroll-state queries — доступним з Chrome 133 — ви можете використовувати CSS для декларативної та більш продуктивної стилізації елементів у цих станах.

.parent {
container-type: scroll-state;
}

Після цього дочірні елементи можуть запитувати, чи батьківський елемент у певному scroll-state:

  • Stuck state: коли елемент прилипає.
  • Snapped state: коли елемент snap-нутий.
  • Scrollable state: коли елемент має overflow.
.scroller {
overflow-x: scroll;
scroll-snap-type: x mandatory;
> div {
container-type: scroll-state;
scroll-snap-align: center;
@supports (container-type: scroll-state) {
> * {
transition: opacity .5s ease;
@container not scroll-state(snapped: x) {
opacity: .25;
}
}
}
}
}
Демо scroll-state queries: елементи змінюють прозорість залежно від snapped стану.
10

#Tree counting functions

Поступові анімації, хтось?

Звичайний метод створення поступових (staggered) анімацій для елементів списку, де кожен елемент з'являється послідовно, вимагає підрахунку DOM-елементів і жорсткого кодування цих значень у custom properties (наприклад, --index: 1;, --index: 2;) за допомогою селекторів :nth-child.

Нові функції sibling-index() та sibling-count() полегшують життя, оскільки надають нативну обізнаність про позицію елемента серед його сусідів.

li {
/* Створюємо поступову затримку. */
/* Віднімаємо 1, бо sibling-index() починається з 1, */
/* забезпечуючи, що перший елемент стартує одразу (0s). */
transition: opacity 0.25s ease, translate 0.25s ease;
transition-delay: calc(0.1s * (sibling-index() - 1));
@starting-style {
opacity: 0;
translate: 1em 0;
}
}
Демо sibling-index(): поступова анімація появи елементів списку.
11

#scrollIntoView() container

Іноді прокрутка лише найближчого ancestor scroller — це все, що вам потрібно.

Опція container для Element.scrollIntoView дозволяє виконуватиscrollIntoView, прокручуючи лише найближчий ancestor scroll container. Це надзвичайно корисно, якщо у вас є вкладені scroll-контейнери.

slideList.addEventListener('click', (evt) => {
// scrollIntoView автоматично визначить позицію.
evt.target.targetSlide.scrollIntoView({container: 'nearest', behavior: 'smooth'});
});
Демо scrollIntoView з опцією container: 'nearest' для вкладених scroll-контейнерів.
12

#Nested View Transition Groups

Зберігайте 3D та clipping ефекти під час view transition.

Nested view transition groups — це розширення для view transitions, яке дозволяє вкладати псевдо-елементи ::view-transition-group один в одного.

Коли view transition groups вкладені, замість того щоб розміщувати їх усі як сусіди під одним псевдо-елементом ::view-transition, можна зберегти 3D та clipping ефекти під час переходу.

.card {
view-transition-name: card;
overflow: clip;
}
.card img {
view-transition-name: photo;
view-transition-group: nearest;
}
::view-transition-group-children(card) {
overflow: clip;
}

Для браузерів без підтримки, перегляньте цей запис:

Демо nested view transition groups: 3D-ефект зберігається під час переходу.
13

#DOM State-Preserving Move

Переміщуйте iframe та відео по DOM без їх перезавантаження.

Використання insertBefore для переміщення елемента в DOM є деструктивним. Якщо ви переміщуєте відео, що програється, або iframe за допомогою insertBefore, він перезавантажується і повністю втрачає свій стан.

Однак, з Chrome 133, ви можете використовувати moveBefore. Він працює точно так само як insertBefore, але зберігає елемент живим під час переміщення.

const $newSibling = getRandomElementInBody();
const $iframe = document.querySelector('iframe');
document.body.moveBefore($iframe, $newSibling);

Це означає, що відео продовжують грати, iframe'и не перезавантажуються, CSS-анімації не перезапускаються, а поля введення зберігають фокус — навіть коли ви активно reparent-ите їх по вашому layout.

Порівняння поведінки insertBefore та moveBefore: відео продовжує грати.

#🛠️ Оптимізована ергономіка

Ергономічний глиняний стілець

Ці модулі — не просто plug-and-play; вони справжні хамелеони, що дозволяють користувачам перевизначати інтерфейс, функціональність та естетику аж до атомарного рівня.

14

#Розширена функція attr()

Типізовані значення для attr() за межами простих рядків.

CSS-функція attr(), яка дозволяє використовувати значення HTML-атрибута у вашому CSS, отримала потужне оновлення.

Раніше attr() можна було використовувати лише в межах властивості content псевдо-елементів і вона могла повертати значення лише як CSS-рядок. Оновлена функція attr()розширює свої можливості, дозволяючи використовувати її з будь-якою CSS-властивістю, включаючи custom properties.

div {
color: attr(data-color type(<color>), red);
}
.card {
/* card-1, card-2, card-3, тощо */
view-transition-name: attr(id type(<custom-ident>), none);
view-transition-class: card;
}
.star-rating {
--percent-fill: calc(attr(data-rating type(<number>)) * 20%);
background: linear-gradient(to right, gold var(--percent-fill), transparent var(--percent-fill));
&::after {
content: attr(data-rating);
}
}
Демонстрація функції attr() з рейтингом зірок
15

#ToggleEvent.source

Дізнайтеся, який елемент був відповідальним за toggle цільового елемента.

Коли popover, <dialog> або <details> елемент перемикається, може бути цікаво знати, який елемент був відповідальним за це. Наприклад, знати, чи користувач натиснув кнопку "Прийняти cookies" чи "Відхилити cookies" для закриття cookie-банера — це дуже важлива деталь.

<div id="cookiebanner" popover="auto">
<p>Бажаєте печиво?</p>
<button id="yes" commandfor="cookiebanner" command="hide-popover">Так</button>
<button id="no" commandfor="cookiebanner" command="hide-popover">Ні</button>
</div>
$cookiebanner.addEventListener('toggle', event => {
if (event.source == $btnYes) {
// Дати користувачу cookie
} else if (event.source == $btnNo) {
// Не давати користувачу cookie
}
});
16

#text-box features

Бездоганно центруйте текст вертикально.

Content box шрифту визначається внутрішніми метриками — конкретно ascent та descent, які резервують простір для акцентів та висячих символів.

Ілюстрація, що показує лінії ascender та descender шрифту
Ілюстрація, що показує лінії ascender та descender шрифту. (Джерело: Material Design)

Оскільки візуальні межі латинського тексту — це cap height та alphabetic baseline, а не ascent та descent, текст виглядатиме оптично зміщеним від центру, навіть коли він математично центрований всередині контейнера.

Ілюстрація, що показує cap height шрифтуІлюстрація, що показує baseline шрифту
Ілюстрації cap height та baseline шрифту. (Джерело: Material Design)

Властивості text-box роблять можливим тонший контроль вертикального вирівнювання тексту, дозволяючи бездоганно центрувати текст вертикально.

h1, button {
text-box: trim-both cap alphabetic;
}
17

#Функція shape()

CSS-функція для складних, адаптивних форм.

Нова функція shape() дозволяє обрізати елемент до складної, неполігональної, адаптивної форми в CSS. Це чудовий варіант для обрізних масок за допомогою clip-path: path(), і безшовно працює з CSS custom properties для визначення координат та контрольних точок.

Приклад форми прапору, створеної за допомогою shape()
Приклад форми прапору, створеної за допомогою shape().
.flag {
clip-path: shape(from 0% 20px,
curve to 100% 20px with 25% 0% / 75% 40px,
vline to calc(100% - 20px),
curve to 0% calc(100% - 20px)
with 75% 100% / 25% calc(100% - 40px),
close
);
}

У цьому прикладі горизонтальні координати використовують відсотки для масштабування з шириною елемента, а вертикальні координати для висоти кривої використовують фіксовані значення в пікселях, створюючи адаптивний ефект, де хвиля прапора залишається сталою незалежно від розміру елемента.

Ще один приклад використовує blob generator для shape(), щоб створити цікавий ефект рамки:

Приклад blob-форми, створеної за допомогою shape()
Приклад blob-форми для ефекту рамки.
18

#if() statements

Умовні оператори у вашому CSS для динамічної стилізації.

Функція if() в CSS дозволяє встановлювати різні значення для властивості на основі умовного тесту. Думайте про неї як про тернарний оператор у JavaScript, але для ваших стилів.

if(condition-1: value-1; condition-2: value-2; else: fallback-value);

Наразі if() можна використовувати з трьома типами запитів:

  • media(): для media queries.
  • supports(): для feature queries.
  • style(): для style queries.
.responsive-layout {
display: flex;
flex-direction: if(media(orientation: landscape): row; else: column);
}
19

#Custom Functions

Перевикористовувані функції для чистіших, підтримуваних стилів.

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

/* Функція negate повертає від'ємне значення */
@function --negate(--value) {
result: calc(-1 * var(--value));
}
/* Використання */
html {
--gap: 1em;
padding: --negate(var(--gap));
}
/* Умовно застосовує радіус, поки ви не в (default: 4px) від краю екрана */
@function --conditional-radius(--radius, --edge-dist: 4px) {
result: clamp(0px, ((100vw - var(--edge-dist)) - 100%) * 1e5, var(--radius));
}
/* використання */
.box {
border-radius: --conditional-radius(1rem);
}

Одна з моїх улюблених CSS-функцій — це умовно заокруглений border-radius. Функція прибирає border-radius елемента, коли він опиняється на відстані менше заданої від краю viewport:

Демо умовного border-radius: заокруглення зникає біля краю екрану.
20

#Expanded range syntax

Синтаксис діапазонів у style queries та if() statements.

Одне приємне оновлення цього року — можливість використовувати синтаксис діапазонів у style queries та if() statements. Media queries та container queries вже підтримували цю можливість, але раніше style queries вимагали точного збігу значення.

Тепер ви можете типізувати ваші значення та використовувати їх з операторами порівняння як <, >, <= та >=.

.card-container {
container-name: weather;
--rain-percent: attr(data-rain-percent type(<percentage>));
}
@container style(--rain-percent > 45%) {
.weather-card {
background: linear-gradient(140deg, blue, lightblue);
}
}

Тепер, якщо шанс дощу більший за 45%, картка отримає синій фон.

Картки погоди з різними кольорами фону та іконками залежно від погоди та ймовірності дощу
Картки погоди з різними кольорами фону залежно від ймовірності дощу.
21

#Stretch sizing keyword

Зробіть елемент таким, щоб він заповнював свій containing block, незалежно від box-sizing.

Ключове слово stretch — це ключове слово для використання з CSS-властивостями розмірів (такими як width та height), яке дозволяє елементам рости, щоб точно заповнити доступний простір їхнього containing block.

Це схоже на 100%, за винятком того, що результуючий розмір застосовується до margin box елемента замість box, визначеного box-sizing.

.element {
height: stretch;
}
22

#corner-shape

Форми кутів за межами заокруглених країв.

Цього року CSS дає нам більше контролю над формою наших елементів з новою властивістюcorner-shape. Ця експериментальна функція дозволяє кастомізувати форму кутів за межами стандартних заокруглених кутів, доступних з border-radius.

Тепер ви можете створювати різноманітні стилі кутів:

  • round — заокруглений
  • bevel — скошений
  • notch — виріз
  • scoop — зачерпнутий
  • squircle — суперекліпс
Демо різних стилів corner-shape: round, bevel, notch, scoop, squircle.
.top-left-petal {
grid-area: 1 / 1 / 3 / 3;
corner-shape: round round scoop;
border-radius: 50%;
}
CSS квітка, створена за допомогою властивості corner-shape
CSS квітка, створена за допомогою властивості corner-shape.

Для ще більшого контролю ви можете використовувати функцію superellipse()для створення будь-якої неперервної кривої, що дозволяє точно налаштовані та унікальні дизайни кутів.

📊 Оновлення Baseline у 2025

  • ✓Container Queries досягли Baseline Widely available
  • ✓Interop 2025 приніс великі покращення для CSS anchor positioning
  • ✓Popover API з'явився у всіх браузерних рушіях
  • ✓CSS тригонометричні функції стали Baseline widely available
  • ✓Same-document view transitions стали Baseline Newly available