
Привет, друзья!
В данной заметке я расскажу вам о некоторых полезных возможностях, предоставляемых современным CSS. Также мы немного поговорим о полезных "фичах", которые ждут нас в ближайшие 2 года.
"Полезный" означает, что я либо часто использую фичу в своих проектах, либо с нетерпением жду такой возможности.
Существующие возможности
Начнем с существующих возможностей, поддерживаемых большинством браузеров.
:not()
Функция-псевдокласс :not() позволяет стилизовать элементы, которые не совпадают ни с одним селектором из указанного списка. Список может состоять как из одного, так и из нескольких селекторов, разделенных запятыми. Селекторы могут быть как простыми, так и сложными.
Синтаксис
:not(selector1, selector2, ...selectorN) {}
Пример
Предположим, что у нас имеется такой инпут:
<input type="text" placeholder="Enter some text..." required />
Мы хотим, чтобы в невалидном состоянии границы этого поля были красного цвета. "Невалидность" поля можно стилизовать с помощью псевдокласса :invalid. При этом, стилизация невалидности не должна влиять на стилизацию наведения и фокусировки. Это можно реализовать следующим образом:
input:invalid:not(:hover, :focus) { border-color: red; } /* отключаем подсветку */ :invalid { box-shadow: none; } :-moz-submit-invalid { box-shadow: none; } :-moz-ui-invalid { box-shadow: none; }
Лирическое отступление: стилизация placeholder
Раньше заменители текста приходилось стилизовать так:
input.placeholder { color: #0275d8; font-size: 0.8rem; opacity: 0.8; } input:-moz-placeholder { color: #0275d8; font-size: 0.8rem; opacity: 0.8; } input::-moz-placeholder { color: #0275d8; font-size: 0.8rem; opacity: 0.8; } input:-ms-input-placeholder { color: #0275d8; font-size: 0.8rem; opacity: 0.8; } input::-webkit-input-placeholder { color: #0275d8; font-size: 0.8rem; opacity: 0.8; }
Сейчас это можно делать с помощью псевдокласса ::placeholder (спасибо dynamicult):
input::placeholder { color: #0275d8; font-size: 0.8rem; opacity: 0.8; }
:empty
Псевдокласс :empty позволяет стилизовать элементы, которые не имеют потомков. К потомкам относятся элементы, текст и (sic!) пробелы.
Синтаксис
:empty {}
Пример
Предположим, что у нас имеется такой список ссылок:
<ul> <li><a href="#">Link 1</a></li> <li> <a href="#" target="_blank" rel="noopener noreferrer">Link 2</a> </li> <li></li> </ul>
С такими стилями:
ul { display: inline-flex; flex-direction: column; /* отступы между элементами списка */ gap: 0.5rem; list-style: none; }
Допустим, что список формируется динамически и последний элемент по какой-то причине оказался пустым, например, в объекте не было url для ссылки. Тогда после второго элемента получим лишний отступ:

Скрываем пустые элементы списка с помощью :empty:
li:empty { display: none; }
Лишнего отступа больше нет:

Лирическое отступление: невидимый контент
display: none; полностью скрывает элемент. Сделать элемент невидимым, но доступным для устройств чтения с экрана, можно следующим образом:
.sr-only { background: none; border: none; color: none; cursor: none; height: 0; margin: 0; opacity: 0; outline: none; overflow: hidden; padding: 0; pointer-events: none; position: absolute; user-select: none; visibility: hidden; white-space: nowrap; width: 0; z-index: -1; }
:is() и :where()
Функции-псевдоклассы :is() и :where() позволяют стилизовать элементы, совпадающие с любым селектором из указанного списка. Список может состоять как из одного, так и из нескольких селекторов, разделенных запятыми. Селекторы могут быть как простыми, так и сложными.
Разница между :is() и :where() заключается в том, что :is() принимает специфичность самого конкретного селектора из списка, а специфичность :where() всегда равняется 0.
Синтаксис
:is(selector1, selector2, ...selectorN) {} :where(selector1, selector2, ...selectorN) {}
Поддержка :is() — 94.9%.
Поддержка :where() — 91.61%.
Пример
Предположим, что мы хотим стилизовать ссылки, находящиеся только в шапке или подвале страницы:
:is(header, footer) a:hover { color: green; }
Для понимания того, насколько :is() и :where() могут уменьшить количество шаблонного кода, рекомендую взглянуть на этот пример.
:focus-within
Псевдокласс :focus-within позволяет стилизовать элементы, которые либо сами находятся в фокусе (в этом случае :focus-within аналогичен псевдоклассу :focus), либо имеют потомков, находящихся в фокусе.
Синтаксис
:focus-within {}
Пример
Предположим, что у нас имеется такой инпут с подписью и иконкой:
<div class="form-field"> <label> <span>Some text:</span> <input type="text" placeholder="Enter some text..." required /> </label> <svg viewBox="0 0 60 60"> <path d="M48.014,42.889l-9.553-4.776C37.56,37.662,37,36.756,37,35.748v-3.381c0.229-0.28,0.47-0.599,0.719-0.951 c1.239-1.75,2.232-3.698,2.954-5.799C42.084,24.97,43,23.575,43,22v-4c0-0.963-0.36-1.896-1-2.625v-5.319 c0.056-0.55,0.276-3.824-2.092-6.525C37.854,1.188,34.521,0,30,0s-7.854,1.188-9.908,3.53C17.724,6.231,17.944,9.506,18,10.056 v5.319c-0.64,0.729-1,1.662-1,2.625v4c0,1.217,0.553,2.352,1.497,3.109c0.916,3.627,2.833,6.36,3.503,7.237v3.309 c0,0.968-0.528,1.856-1.377,2.32l-8.921,4.866C8.801,44.424,7,47.458,7,50.762V54c0,4.746,15.045,6,23,6s23-1.254,23-6v-3.043 C53,47.519,51.089,44.427,48.014,42.889z" fill="currentColor" /> </svg> </div>
Обратите внимание на значение атрибута fill элемента path.
И такими стилями:
.form-field { color: darkslategray; position: relative; width: max-content; } .form-field label { align-items: center; display: flex; } .form-field input { border: 2px solid darkslategray; margin-left: 0.5rem; outline: none; padding: 0.5rem; /* отступ для иконки - 20px + 5px + 5px */ padding-right: 30px; } .form-field svg { height: 20px; position: absolute; right: 5px; top: 50%; transform: translateY(-50%); width: 20px; }

Допустим, что при наведении и фокусировке мы хотим менять цвет подписи, границы поля и заливки иконки.
С инпутом все просто:
.form-field input:hover { border-color: deepskyblue; } .form-field input:focus { border-color: mediumseagreen; }
С наведением тоже:
.form-field:hover { color: deepskyblue; }

Но следующее работать не будет, поскольку элемент div не является фокусируемым (focusable):
.form-field:focus { color: mediumseagreen; }
Здесь на помощь приходит :focus-within:
.form-field:focus-within { color: mediumseagreen; }
Scroll Snap
Scroll Snap позволяет реализовывать прокручиваемые слайдеры (scrollable sliders). Основными свойствами данной модели являются:
- scroll-snap-type — определяет строгость привязки контейнера к контрольным точкам;
- scroll-snap-align — определяет контрольные точки для прокрутки.
Синтаксис (основные значения)
scroll-snap-type: x | y | both [mandatory | proximity]; scroll-snap-align: start | center | end;
Поддержка scroll-snap-type — 94.98%.
Поддержка scroll-snap-align — 94.75%.
Пример
Прокручиваемый слайдер с тремя изображениями котиков:
<div class="slider"> <img src="https://images.unsplash.com/photo-1529257414772-1960b7bea4eb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80" alt="" /> <img src="https://images.unsplash.com/photo-1598188306155-25e400eb5078?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1548&q=80" alt="" /> <img src="https://images.unsplash.com/photo-1517331156700-3c241d2b4d83?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1768&q=80" alt="" /> </div>
* { margin: 0; } body { align-items: center; display: flex; height: 100vh; justify-content: center; overflow: hidden; } .slider { display: flex; overflow-x: scroll; position: relative; /* ! */ scroll-snap-type: x mandatory; /* стилизация скроллбара для Firefox */ scrollbar-color: hotpink whitesmoke; scrollbar-width: thin; width: 480px; } .slider > img { object-fit: cover; /* ! */ scroll-snap-align: start; width: 100%; } /* стилизация скроллбара для Webkit */ .slider::-webkit-scrollbar { height: 6px; } .slider::-webkit-scrollbar-track { background-color: whitesmoke; } .slider::-webkit-scrollbar-thumb { background-color: hotpink; }
scroll-behavior
Свойство scroll-behavior, как следует из названия, определяет поведение прокрутки.
Синтаксис (основные значения)
scroll-behavior: auto | smooth;
Пример
Рассматриваемое свойство позволяет легко реализовать прокрутку к определенной позиции на странице без помощи JavaScript:
<!-- якорь --> <a id="top"></a> <div class="page-content"> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> </div> <a href="#top" class="top-link"> <img src="https://cdn-icons-png.flaticon.com/512/892/892692.png" alt="" /> </a>
* { margin: 0; } html, body { /* ! */ scroll-behavior: smooth; } .page-content { display: flex; flex-direction: column; } p { display: grid; font-size: 4rem; height: 100vh; place-content: center; } .top-link { bottom: 1rem; left: 50%; position: fixed; transform: translateX(-50%); } .top-link img { width: 40px; }
accent-color
Свойство accent-color позволяет менять цвет таких элементов, как <input type="checkbox" />, <input type="radio" />, <input type="range" /> и <progress />.
Синтаксис
accent-color: auto | <color>;
Пример
<input type="checkbox" checked /> <input type="radio" checked /> <input type="range" min="0" max="10" value="5" /> <progress max="100" value="50"></progress>
body { /* ! */ accent-color: deepskyblue; align-items: start; display: flex; flex-direction: column; gap: 1rem; }

Теперь в простых случаях можно обходиться без создания кастомных чекбоксов, радио-кнопок и т.д.
Будущие возможности
Теперь поговорим о наиболее интересных возможностях, которые ждут нас в ближайшем будущем.
Вложенность
Вложенность — одна из самых ожидаемых возможностей, давно реализованных в таких CSS-препроцессорах, как Sass и Less.
Синтаксис похож на Sass, за исключением того, что использование символа & в качестве родительского селектора является обязательным.
Пример
<article class="article"> <h2 class="title">Title</h2> <div class="info"> <p class="author">Author</p> <time datetime="2022-08-01" class="date">01.08.2022</time> </div> <p class="summary">Summary</p> </article>
/* стилизуем информацию об авторе статьи */ .article { & .info { /* .article .info .author */ & .author { /* ... */ } } } /* в Sass можно опускать `&` */ .article { .info { .author { /* ... */ } } }
Еще одно отличие от Sass состоит в том, что, судя по всему, в CSS нельзя будет "склеивать" селекторы. Например, если у нас имеется такая разметка:
<article class="article"> <h2 class="article__title">Title</h2> </article>
В Sass стилизовать заголовок можно следующим образом:
.article { &__title { /* ... */ } }
Если вас интересует более подробная информация о вложенности, рекомендую взглянуть на эту статью.
:has()
Функция-псевдокласс :has() предназначена для стилизации элементов, совпадающих хотя бы с одним селектором из списка, переданного в качестве аргумента. Прелесть данной функции состоит в том, что она позволяет стилизовать родительские элементы.
Синтаксис
:has(selector1, selector2, ...selectorN) {}
Примеры
/* стилизуем ссылки, которые содержат изображения в качестве непосредственных потомков */ a:has(> img) {} /* стилизуем `img`, которые находятся в `figure`, которые имеют `figcaption` */ figure:has(figcaption) img {}
color-mix()
Функция color-mix принимает 2 цвета и возвращает результат их смешивания в указанном цветовом пространстве и на указанный процент. Дефолтным цветовым пространством является LCH.
Синтаксис:
color-mix(in <colorspace>?, <color1> <percentage>?, <color2> <percentage>?)
Пример
<div class="box brand"></div> <div class="box darken"></div> <div class="box lighter"></div>
:root { --brand: deepskyblue; /* ! */ --darken: color-mix(var(--brand) 25%, #333); --lighter: color-mix(var(--brand) 25%, #eee); } .box { width: 100px; height: 100px; } .box.brand { background-color: var(--brand); } .box.darken { background-color: var(--darken); } .box.lighter { background-color: var(--lighter); }
scope
Директива @scope предназначена для определения области видимости стилей.
Синтаксис
@scope <selector> { <stylesheet> }
Пример
Вернемся к примеру из раздела про вложенность:
.article { & .info { & .author { /* ... */ } } }
Если после этих стилей будет определено что-то вроде:
.section { & .info { & .author { /* ... */ } } }
То специфичность селекторов .article .info .author и .section .info .author будет одинаковой и стили, определенные в .section, перезапишут стили, определенные в .article. @scope позволяет решить данную проблему за счет инкапсуляции стилей в собственной области видимости:
@scope .article { /* стили применяются только к элементам, находящимся в элементе с классом `article` */ & .info { & .author { /* ... */ } } } @scope .section { /* стили применяются только к элементам, находящимся в элементе с классом `section` */ & .info { & .author { /* ... */ } } }
Справедливости ради следует отметить, что на сегодняшний день существует еще одно средство для решения проблемы правильного порядка определения стилей — директива @layer, позволяющая определять так называемые каскадные слои стилей (cascade layers). Однако, на мой взгляд, эта директива больше подходит для использования в CSS-фреймворках, чем для локального применения. Например, @layer активно используется в таком фреймворке, как TailwindCSS.
selectmenu
Элемент selectmenu — это своего рода стилизуемый вариант элемента select.
Синтаксис
selectmenu::part(<part>) {}
Пример
<selectmenu class="select-menu"> <option>Option 1</option> <option>Option 2</option> <option>Option 3</option> </selectmenu>
.select-menu::part(button) { background-color: #f00; border-radius: 5px; color: white; padding: 5px; } .select-menu::part(listbox) { border-radius: 5px; border: 1px solid red; margin-top: 5px; padding: 10px; }
anchor()
Последняя возможность, о которой я хочу рассказать, это функция anchor().
Данная функция является альтернативой позиционированию элементов с помощью position: relative и position: absolute. Она позволяет привязывать одни элементы к другим независимо от их места в иерархии DOM.
anchor() также будет предоставлять возможность определять границы позиционирования и другие интересные фичи. Перейдите по ссылке, если хотите получить более подробную информацию о рассматриваемом интерфейсе (да, это целый интерфейс, а не одна функция).
Подробнее о новых и ожидаемых возможностях CSS можно почитать в этой замечательной статье.
Пожалуй, это все, чем я хотел поделиться с вами в этой заметке. Надеюсь, вы узнали что-то новое и не зря потратили время.
Благодарю за внимание и happy coding!

