Безопасная работа с современным CSS: якоря, полосы сетки, область применения и функция light-dark()
Узнайте, как позиционирование якорей, модальные окна, мозаичная композиция по сетке, переходы между видами, функции light-dark() и @scope заменяют библиотеки JavaScript, а также как использовать каждый из этих инструментов с резервными вариантами.
Источники: CSS Can Already Do This. Developers Are Still Scared to Ship It.
Многие пакеты фронт-энда по-прежнему включают библиотеки, единственная задача которых — определение местоположения подсказок, такие как Floating UI или её предшественник Popper, а также другие библиотеки для формирования сетки в стиле Pinterest. Теперь браузеры могут выполнять обе эти функции нативно, наряду с несколькими другими задачами, которые раньше требовали JavaScript, однако лишь немногие команды устранили эти зависимости. В этом руководстве рассматриваются пять функций CSS, способных заменить распространённый код библиотек, объясняется, как работает каждая из них, и показано, как внедрить их без риска из-за неравномерной поддержки браузерами.
Одновременно желаемые и избегаемые
Опросы среди разработчиков хорошо отражают эту ситуацию. В результатах исследования State of CSS за 2026 год позиционирование анкоров было признано самой интересной новой функцией среди разработчиков, вытеснив :has(), который сейчас настолько широко используется, что уже не кажется чем-то новым. В том же опросе спрашивали, какие функции люди намеренно избегают из-за отсутствия надежной поддержки в браузерах, и позиционирование анкоров также оказалось в числе лидеров этого списка. Для преодоления этой проблемы в основном необходимо использовать функции, реализованные с помощью запросов к функциям. Цифры из опросов и статусы поддержки быстро меняются, поэтому следует рассматривать их как моментальную снимку и проверять актуальные данные о совместимости перед тем, как полагаться на какую-либо функцию.
Позиционирование анкоров вместо библиотек подсказок
Подход с JavaScript заключается в привязке плавающего элемента к его триггеру путем измерения обоих элементов и пересчёта их положения при каждом прокрутке или изменении размера. Метод позиционирования анкоров переносит эту логику в CSS.
Ниже триггер указывает имя, а меню позиционируется относительно него. Последнее объявление заменяет функцию обнаружения столкновений из библиотеки:
.trigger {
anchor-name: --menu-trigger;
}
.menu {
position: fixed;
position-anchor: --menu-trigger;
top: anchor(bottom);
left: anchor(center);
position-try-fallbacks: flip-block, flip-inline;
}
У каждого элемента есть своя конкретная роль:
anchor-nameприсваивает элементу идентификатор в виде пунктирной линии, по которому другие элементы могут определять его положение.position-anchorуказывает элементу, который положение ему следует принимать, имя соответствующего анкора.anchor()считывает координаты, такие как нижний край или горизонтальный центр анкора, непосредственно из этого элемента.
position-try-fallbacks перечисляет альтернативы, которые пробует браузер, когда элемент может выйти за пределы области отображения; flip-block и flip-inline отражают его вертикально или горизонтально.Сочетание с атрибутом popover
Атрибут popover, также включенный в стандарт, но часто игнорируемый, регулирует поведение выпадающего меню: кнопка с атрибутом popovertarget активирует элемент с соответствующим id, для этого не требуется скрипт:
<button popovertarget="menu">Options</button>
<div id="menu" popover anchor-name-target="--menu-trigger">...</div>
Будьте осторожны с этим фрагментом кода: anchor-name-target не является стандартным атрибутом HTML, поэтому не копируйте его в таком виде. Соедините поп-овер с его точкой привязки в CSS с помощью anchor-name и position-anchor, как показано выше. Некоторые браузеры также рассматривают кнопку, вызывающую поп-овер, как имплицитную точку привязки для него, открытого с помощью popovertarget; перед тем как полагаться на это, ознакомьтесь с актуальной документацией.
Встроенная функция popover предусматривает отображение на верхнем уровне, поэтому меню появляется над всем остальным без конфликтов z-index в приложении, автоматически закрывается при нажатии снаружи или клавиши Escape, а также обеспечивает корректную обработку фокуса при открытии и закрытии. Он не удерживает фокус, как это делает модальное dialog, поэтому для настоящего модального контента используйте dialog вместо него.
Чтобы узнать больше о браузерных API, которые делают пакеты npm ненужными, ознакомьтесь с нашим обзором нативных браузерных API, заменяющих популярные пакеты.
Нативная композиция с полосами сетки
С момента появления CSS Grid в браузерах в 2017 году для создания мозаичных макетов всё ещё требовался JavaScript, поэтому библиотека Masonry и подобные пакеты устанавливались миллионы раз. Но это меняется. Приведённое ниже правило определяет полный макет:
.gallery {
display: grid-lanes;
gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
Каждая карточка размещается в той колонке, у которой в данный момент больше всего свободного пространства, благодаря чему элементы располагаются плотно без лишних промежутков. Не используется ResizeObserver, а также автоматическое перераспределение колонок в скрипте после загрузки изображения. Вопрос синтаксиса вызывал многолетние споры среди производителей браузеров: Apple и Google выступали за разные подходы, прежде чем было найдено компромиссное решение в виде display: grid-lanes.
Согласно информации о поддержке на момент написания текста, Safari первым внедрил эту функцию, тогда как Chrome и Firefox оставили её в режиме флагов. Это объясняет, почему ею пользуются так мало сайтов, и почему сейчас стоит её реализовать. Необходимо разместить grid-lanes внутри блока @supports (display: grid-lanes), а в качестве запасного варианта использовать обычную сетку.
Переходы между видами отображения: с большой осторожностью
Здесь осторожность оправдана. Поддержка API View Transition существует, но является неравномерной, из-за чего она наряду с позиционированием анкоров относится к числу наиболее избегаемых функций, и такое сомнение вполне обосновано.
Основной механизм прост: оборачиваем обновление DOM в document.startViewTransition(), и браузер делает снимки старого и нового состояний, затем плавно переходит от одного к другому без использования библиотек анимации и без необходимости вручную вести учет изменений до и после. Приведенные ниже псевдоэлементы управляют этим стандартным переходом, здесь сокращая его до 0,3 секунды:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.3s;
}
Сегодня ему можно доверять в качестве инструмента прогрессивного улучшения. Браузер, не имеющий соответствующего API, просто переходит в новое состояние, поэтому ничего не ломается. Это делает его безопасным для использования в некритичных ситуациях, таких как замена страниц или переупорядочивание списка. В скриптах необходимо проверить наличие функции document.startViewTransition перед её вызовом, а при отсутствии — применить обновление напрямую.
Одна палитра для светлого и тёмного режимов с помощью light-dark()
В типичном тёмном режиме используются две полные системы цветов: одна внутри медиа-запроса prefers-color-scheme: dark, а другая — снаружи. Функция light-dark() объединяет каждую пару цветов в одно заявление.
В приведённом ниже фрагменте сначала с помощью атрибута color-scheme устанавливаются оба режима, что необходимо для работы функции light-dark(), а затем каждому свойству присваиваются значения для светлого и тёмного режимов одновременно:
:root {
color-scheme: light dark;
}
body {
background: light-dark(#ffffff, #1a1a1a);
color: light-dark(#111111, #eeeeee);
}
После установки color-scheme каждый вызов light-dark() возвращает соответствующее значение в зависимости от настроек системы пользователя, что позволяет избежать дублирования запросов к медиа-запросам в стилевом листе. Для других способов реализации тематизации см. наше сравнение техник темного режима.
Ограничение действия стилей с помощью @scope
Одной из основных причин использования CSS-in-JS или строгой схемы именования BEM является утечка специфичности: правило .card, предназначенное для одного компонента, также применяется к несвязанному элементу .card, находящемуся глубже в маркировке другого компонента. Признак @scope нативным образом ограничивает действие стилей определенной поддереву.
В приведенном ниже правиле стили параграфов применяются внутри .card, но не распространяются на элементы типа .card-nested:
@scope (.card) to (.card-nested) {
p {
color: var(--card-text);
}
}
Первый селектор определяет корень области применения. Пункт to устанавливает нижнюю границу, создавая пространство, в котором вложенный компонент может управлять собственными стилями вместо того, чтобы наследовать их от родителя. Для этого не требуется никаких шагов сборки, генерации имен классов или выполнения JavaScript во время работы приложения. Это не заменит всё то, что делает CSS-in-JS, например расчёт стилей на основе свойств компонентов, но оно устраняет одну из основных причин, по которым команды его используют.
Браузер — это экосистема
Все пять функций уже доступны хотя бы в одном реальном браузере сегодня, и ни одна из них не зависит от React, плагина для сборки или новой версии фреймворка. Разница между тем, что может делать CSS, и тем, что используют команды, во многом обусловлена привычкой: на протяжении десяти лет решение проблемы интерфейса означало установку какого-либо пакета.
Выберите место в вашем проекте, где используется обходной метод на JavaScript для решения одной из этих проблем, замените его на код внутри @supports и посмотрите, что сломается. Обычно это будет меньше, чем вы ожидаете.
Частые вопросы
Готово ли позиционирование элементов к использованию в продакшене?
Этот метод уже используется в продакшене и может применяться сегодня при условии проверки через @supports. То, что он находится в списке наиболее нежелательных решений, связано с осторожностью из-за неравномерной поддержки в браузерах, а не с недостатками самого функционала. Правильным подходом будет сочетание этого метода с запасным вариантом, например простым статическим позиционированием.
Нужна ли все еще библиотека для мозаичного макетирования?
Для нового проекта, скорее всего, нет, при условии что display: grid-lanes находится внутри блока @supports с обычной сеткой в качестве запасного варианта. Для существующего макета, требующего строгой совместимости с браузерами, стоит еще некоторое время использовать эту библиотеку.
Какую функцию стоит внедрить в первую очередь?
light-dark(). Для каждого значения цвета требуется лишь одна строка кода, на момент написания этого текста она пользовалась наибольшей поддержкой среди всех пяти вариантов, к тому же она устраняет блок дублирующегося CSS для темного режима без риска нарушения макета.
Основные выводы
- Рассматривайте каждую функцию как элемент прогрессивного улучшения: использовайте нативное поведение там, где оно поддерживается, или существующие решения в других местах.
- Позиционирование элементов с помощью анкора в сочетании с
popoverпокрывает большинство потребностей в подсказках и выпадающих списках, но привязывайте анкор в CSS, а для настоящего модального окна используйтеdialog. grid-lanesи переходы между видами представления наиболее нестабильны; используйте@supportsили проверку во время выполнения для их активации.light-dark()и@scopeпозволяют немедленно удалять код и несут наименьший риск.