# CSS в 2026 году: где JavaScript уже не обязателен Когда работаешь с такими масштабными продуктами, как мы делаем в VK, быстро начинаешь ценить код, который можно не писать. Особенно если речь о небольших сценариях: открыть поповер, анимировать появление блока или подстроить поле под контент. Ещё недавно для таких задач мы почти автоматически тянулись к JavaScript. Но за последние два года CSS сильно расширил свои возможности для задач, связанных с интерфейсом. Я Максим Кузнецов, занимаюсь оптимизацией производительности и улучшением стабильности веб-версий ВКонтакте. В этой статье расскажу, что уже можно использовать как альтернативу JS, что стоит попробовать аккуратно, а за чем пока лучше просто следить. ## Что уже полезно в реальных проектах Начнём с возможностей, которые чаще всего влияют на повседневную разработку: состояние DOM, формы, всплывающие элементы и анимации. Это не самые новые фичи, но именно они чаще всего помогают работать уже без использования JavaScript. ### :has(): стилизация контейнера по состоянию вложенных элементов Поддержка: Chrome, Safari, Firefox :has() далеко не новая тема, разговоры про него идут давно, и во многих проектах он уже активно используется. Но именно сейчас он стал восприниматься как полноценный рабочий инструмент. :has() решает одну из старых проблем CSS: как изменить стиль родителя, если внутри него есть элемент в определённом состоянии. Раньше для этого часто использовали JS. Например, если внутри блока выбран checkbox, скрипт добавлял класс на родительский элемент. Если выбор снимался, класс удалялся. В итоге появлялась вспомогательная логика, которая отвечала только за визуальное состояние. С :has() такой сценарий можно описать сразу в CSS: Ещё несколько типичных примеров: :has() хорошо подходит для локального визуального состояния: выбранный checkbox, невалидное поле, фокус внутри карточки, активный элемент внутри контейнера, наличие определённого дочернего элемента. В нашем коде такие сценарии уже встречаются, и это хороший пример того, как CSS забирает на себя не бизнес-логику, а именно визуальную часть поведения. Если состояние нужно для данных, аналитики, маршрутизации или бизнес-правил, оно остаётся в JS. Но если класс раньше добавлялся только ради оформления, :has() в этом плане часто оказывается проще. ### field-sizing: авторазмер полей без помощи JavaScript Поддержка: Chrome и Safari — есть, Firefox — ещё нет Авторазмер полей формы долго оставался задачей, которую обычно решали через JavaScript. Для textarea приходилось подписываться на input, читать высоту содержимого, сбрасывать текущее значение высоты и выставлять новое. Для input использовали похожие обходные решения: скрытые элементы, измерение текста или дополнительные обёртки. field-sizing закрывает часть этих сценариев на уровне CSS. При значении content поле может подстраиваться под введённое значение. Это полезно для инлайн-редактирования, небольших форм и интерфейсов, где размер поля должен зависеть от содержимого. При этом свойство не отменяет нормальную работу с ограничениями. Для реального интерфейса всё равно нужно задать минимальный и максимальный размер, проверить переносы, длинные значения, мобильную клавиатуру и доступность. Вывод простой: если поле должно просто подстраиваться под контент, для этого уже не всегда нужен отдельный скрипт. ### Popover API: всплывающие элементы без лишнего кода Всплывающие элементы долго требовали большого количества JavaScript. Popover API забирает часть этого базового поведения на себя. Для простых меню, тултипов и небольших поповеров это снижает объём кода, потому что базовое открытие и закрытие можно описать декларативно. Конечно, в сложных компонентах JS всё равно может понадобиться — например, для сложного управления состоянием, вложенных сценариев. Но для базового поведения теперь есть нативная основа, и это уже меняет подход к таким компонентам. ### CSS Anchor Positioning: позиционирование относительно якоря Поддержка: Chrome и Safari — есть, Firefox — нет Тултипы, меню и превью обычно должны появляться рядом с конкретным элементом: кнопкой, аватаром, полем ввода. Раньше для этого часто считали координаты через JS, следили за прокруткой, изменением размеров окна и выходом элемента за границы экрана. CSS Anchor Positioning позволяет связать всплывающий элемент с якорем и описать позицию декларативно. Главная идея здесь не в том, что все утилитарные методы уже завтра станут не нужны. Идея в том, что для части сценариев браузер уже начинает закрывать то, что раньше писали вручную. ### @starting-style: анимация появления без промежуточного состояния Когда элемент впервые появляется в DOM или переходит из display: none в видимое состояние, браузер не всегда знает, от какого значения запускать переход. Поэтому в интерфейсах часто заводят промежуточные состояния: mounted, visible, closing, таймеры и ручное удаление элемента после анимации. @starting-style позволяет задать начальное состояние для такого перехода. Это особенно полезно для dialog, popover, меню и элементов, которые появляются после изменения атрибута или состояния отображения. ### transition-behavior: переходы для дискретных свойств Некоторые CSS-свойства не анимируются плавно. Например, display не имеет промежуточных значений, так как элемент либо отображается, либо нет. transition-behavior: allow-discrete позволяет включить переходы для дискретных свойств и аккуратнее описывать открытие и закрытие элементов. В связке с @starting-style это помогает описывать появление и скрытие поповеров, диалогов и меню без дополнительного слоя состояний в JS. ### Scroll-driven animations: анимации на основе прокрутки Анимации, завязанные на прокрутку, долго делали через обработчики scroll: вычисляли позицию элемента, обновляли CSS-переменные, синхронизировали прогресс и следили за производительностью. Сейчас часть таких задач можно описать через CSS Scroll-driven animations: scroll-timeline, view-timeline, animation-timeline. Типовые сценарии: — индикатор прогресса чтения— появление блока при попадании в область видимости— декоративные scroll-эффекты— анимация, завязанная на положение контейнера прокрутки Это не замена всем scroll-сценариям. Если прокрутка влияет на данные, сбор аналитики, состояние приложения или иную побочную логику, JavaScript остаётся незаменимым. Но если задача визуальная, CSS-вариант следует рассмотреть первым. ## Новые и экспериментальные возможности Теперь перейдём к возможностям, которые интересны, но требуют большей осторожности; часть из них пока доступна не везде, часть лучше использовать как постепенное улучшение, а другая часть показывает, куда в целом развивается CSS. Все возможности из этого раздела перед внедрением нужно отдельно проверять по целевым браузерам. ### sibling-index() и sibling-count(): позиция элемента без ручной передачи индекса Поддержка: только Chrome В повторяющихся элементах, например списках или повседневных сетках, часто нужно знать позицию элемента: для задержки анимации, расчёта смещения, распределения элементов или декоративного эффекта. До появления новых функций обычно использовали один из трёх подходов: — прописывали несколько правил через :nth-child()— передавали индекс через CSS-переменную из шаблона— считали позицию через JavaScript sibling-index() возвращает номер текущего элемента среди соседей, а sibling-count() — общее количество соседних элементов. Ещё один пример — распределение элементов по ширине: Это особенно полезно для декоративных списков, каскадных анимаций и компонентов, где визуальное поведение зависит от порядка элементов. ### if(): условные значения внутри CSS-свойств if() добавляет в CSS возможность выбирать значение свойства по условию. Это не заменяет медиазапросы, @supports или состояние приложения, но позволяет держать часть локальной логики рядом со свойством. media() внутри if() проверяет условия среды, например ширину экрана или тип устройства ввода. А @supports внутри if() проверяет поддержку конкретного CSS-свойства или значения. Например, можно выбрать размер кнопки в зависимости от типа устройства ввода: Здесь media(any-pointer: fine) означает: если у пользователя точное устройство ввода, например мышь или трекпад, можно сделать кнопку компактнее. В остальных случаях оставляем более крупный размер. Можно опираться на ширину вьюпорта: Можно проверять поддержку возможности браузером: А можно выбирать значение на основе другого CSS-свойства: Главная польза if() не в том, что CSS превращается в язык программирования. Польза в локализации условий, иными словами, связанная логика остаётся рядом с тем свойством, на которое влияет. Для боевого кода всё равно нужны запасные значения и проверка через @supports. ### @function: переиспользуемые вычисления в CSS @function позволяет описывать собственные CSS-функции и переиспользовать вычисления без препроцессора. Пример — функция для сетки: Это направление особенно интересно для команд, которые до сих пор держат препроцессор только ради функций или миксинов. ### interest-delay: задержка открытия и закрытия для hover-сценариев Выпадающие элементы часто открываются слишком резко. Пользователь наводит курсор на кнопку, элемент сразу появляется, затем исчезает, если курсор не успел перейти на всплывающую область. В таких сценариях раньше обычно добавляли задержку через JavaScript. interest-delay позволяет задавать задержку для interest-сценариев декларативно. Это может быть полезно для подсказок, hover-карточек, предпросмотров и элементов, которые не должны открываться или закрываться мгновенно. ## Декоративные и нишевые возможности Здесь я собрал фичи для более точной настройки интерфейса (цвета, нестандартные формы и курсор ввода). Они не меняют общий подход к приложению, но могут быть полезны в конкретных местах. ### contrast-color(): автоматический контраст для простых случаев Поддержка: только Safari contrast-color() принимает цвет и возвращает контрастный к нему цвет. В текущей модели функция выбирает между чёрным и белым в зависимости от того, какой вариант лучше читается на переданном фоне. Но contrast-color() нельзя считать заменой дизайн-системе и проверкам доступности. Функция выбирает только между чёрным и белым, а не подбирает цвет из палитры продукта. Абсолютный чёрный и абсолютный белый не всегда подходят визуально. А автоматический выбор контраста не отменяет дизайн-токены, тесты и ручную проверку цветовых пар. ### border-shape: декоративные формы без части ограничений clip-path Поддержка: слабая Здесь поддержка хуже всего, поэтому пока это скорее возможность, за развитием которой стоит следить, а не брать в основу интерфейса. clip-path давно используют для нестандартных форм. Но у него есть ограничение: он обрезает не только сам элемент, но и декоративные эффекты. Например, box-shadow может исчезнуть вместе с областью, которую срезал clip-path. Один из распространённых обходов — filter: drop-shadow(). Но это отдельный приём со своими ограничениями по качеству отрисовки и производительности. border-shape решает похожую задачу иначе: задаёт форму границы и позволяет сохранить декоративные свойства. Свойство поддерживает знакомые функции форм: Но пока border-shape лучше внедрять как прогрессивное улучшение: если браузер не поддерживает свойство, интерфейс должен оставаться нормальным. ### caret-shape: форма текстовой каретки В CSS давно есть caret-color, который задаёт цвет текстовой каретки. caret-shape добавляет ещё один уровень настройки — форму курсора ввода. Доступны три базовых значения: Это нишевая возможность. В обычных формах лучше не менять каретку без причины: пользователи ожидают стандартное поведение поля ввода. Но для редакторов кода, терминальных интерфейсов, обучающих сред и специальных полей свойство может быть полезным. caret-shape позволяет аккуратнее настроить детали интерфейса там, где форма курсора действительно важна. ## Что в итоге? CSS не стал (и не должен был стать) заменой JavaScript. Но они начали лучше дополнять друг друга: JS остаётся там, где нужна логика приложения, а CSS всё чаще закрывает задачи, которые действительно должен закрывать. - css - без javascript - разработка сайтов - веб-разработка - js - html - javascript - фронтенд - frontend - Блог компании VK - CSS - Веб-разработка - Интерфейсы - JavaScript