Привет, Хабр!

В беседах с коллегами я всё чаще замечаю, что многие не в курсе последних фишек CSS. Конечно, у каждого свои причины. Кто-то погряз в повседневной рутине. Кому-то просто неинтересно, что там нового происходит в мире CSS. А кто-то годами сидит на десятилетних подходах и чувствует себя прекрасно.

Честно говоря, как давнему фанату CSS, мне становится немного грустно. Сколько классных возможностей проходит мимо них! А ведь их код мог бы быть короче, надёжнее и понятнее. Именно поэтому я собрал несколько примеров, которые были популярны раньше, и переписал их, используя современные возможности CSS.

Давайте посмотрим, что у меня получилось.

Трюк со свойством padding-top

Раньше на собеседовании очень часто задавали вопрос: «Как будут рассчитываться единицы измерения % для свойства padding?». Если вы никогда не задумывались об этом, то вы не ответите. Правильный ответ — от ширины элемента.

Этот нюанс стал суперполезной фишкой. Когда в вебе появилось больше медиа-контента, перед разработчиками стала задача по корректному отображению этого контента и элементов вокруг него.

В итоге мы получили трюк с использованием вертикального значения свойства padding, основанный на единицах измерения %. Для примера представим, что нам нужно отобразить изображение квадратной формы.

<body>
  <div class="awesome-media-container">
    <!-- атрибут alt пустой, потому что это изображение демонстративное и оно не передаёт какой-либо смысл или пользу -->
    <img src="awesome-picture.webp" class="awesome-media" alt="">
  </div>
</body>
.awesome-media-container {
  position: relative;
  padding-top: 100%;
}

.awesome-media {
  position: absolute;
  top: 0;
  left:0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

Поскольку для элемента с классом .media используется свойства position со значением absolute, браузеры «вытаскивают» его из родителя. Соответственно, без свойства padding у него будет установлено значение 0 для высоты.

Мы же делаем так, чтобы этого не было. Браузеры, увидев значение 100%, рассчитают его относительно ширины, т.е. сделают таким же. Так мы получили желаемое соотношение сторон.

Уверен, вы видели этот подход ранее. Он даже сейчас используется. Но время же прошло. Давно существует свойство aspect-ratio. Оно задаёт соотношение сторон для элемента.

В нашем примере мы установим его для элемента с классом .awesome-media-container.

.awesome-media-container {
  position: relative;
  aspect-ratio: 1;
}

.awesome-media {
  position: absolute;
  top: 0;
  left:0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

Результат мы получили тот же самый. А код стал более «прозрачным»!

Только у меня есть небольшое замечание. Поскольку трюк со свойством padding-top очень старый, то я использовал такой же способ для установки размеров элемента с классом .awesome-media. Я имею в виду свойства .

Можно сделать всё в одно свойство inset! Добавим его со значением 0, чтобы растянуть элемент на всю ширину и высоту родителя.

.awesome-media-container {
  position: relative;
  aspect-ratio: 1;
}

.awesome-media {
  position: absolute;
  inset: 0;
  object-fit: cover;
}

Вот так идеально!

Как надёжно работать с отрицательными значениями для свойства z-index

Свойство z-index скрывает за собой проблемы. Многие фронтендеры используют отрицательные значения, но не понимают, как они работают. Я часто вижу, как используется псевдо-элемент для создания маски у изображения.

Для демонстрации я не буду использовать маску, а задам цвет фону у псевдо-элемента.

<body>
  <div class="awesome-container">
    <div class="awesome-block">
      <span>волшебный текст</span>
    </div>
  </div>
</body>
.awesome-block {
  position: relative;
}

.awesome-block::after {
  content: "";
  background-color: green;
  
  position: absolute;
  inset: 0;
  z-index: -1;
}

А теперь я сломаю всё. Для этого мне нужно добавить только одно свойство background-color. Добавлю его к элементу с классом .awesome-container.

.awesome-container {
  background-color: purple;
}

.awesome-block {
  position: relative;
}

.awesome-block::after {
  content: "";
  background-color: green;
  
  position: absolute;
  inset: 0;
  z-index: -1;
}

Почему так происходит? Дело в том, как работают значения для свойства z-index. Они рассчитываются от элемента, который создаёт специальную область. В стандартах её называют stacking context. Если такой области нет, то ей становится область, созданная корневым элементом, т.е. элемент html. Авторы стандарта назвали её root stacking context.

В нашем примере у псевдо-элемента ::before определено отрицательное значение для свойства z-index. Следовательно, оно должно скрыться за элементом, который создаёт область stacking context. Но его нет! Поэтому псевдо-элемент скрывается за элементом html.

Мы сначала этого не видели, потому что ни у кого не был установлен цвет фона. Но как только установили фон у родительского элемента, он перекрыл область, созданную элементом html. А это привело к тому, что псевдо-элемент скрылся.

Решить эту задачу можно, задав элементу с классом .awesome-block отличное от нуля положительное значение для свойства z-index. Так мы создадим область stacking context.

.awesome-container {
  background-color: purple;
}

.awesome-block {
  position: relative;
  z-index: 1;
}

.awesome-block::after {
  content: "";
  background-color: green;
  
  position: absolute;
  inset: 0;
  z-index: -1;
}

Теперь всё работает так, как задумывалось. Вот только это устаревший подход. В стандартах уже много лет есть свойство isolation. Оно может задать область stacking context. Для этого есть значение isolate.

Давайте добавим его к элементу с классом .awesome-block.

.awesome-container {
  background-color: purple;
}

.awesome-block {
  position: relative;
  isolation: isolate;
}

.awesome-block::after {
  content: "";
  background-color: green;
  
  position: absolute;
  inset: 0;
  z-index: -1;
}

Получили также результат. И больше у нас точно не будет неожиданного результата!

Перечисление селекторов через запятую

Меня очень сильно огорчает, что фронтендеры очень редко используют псевдо-классы :is() и :where(). Почему-то писать полностью сложные селекторы предпочтительнее.

В общем, давайте расскажу, в каких случаях псевдо-классы помогают. Я даже нашёл реальный пример. В нём автор реализует плавующую метку для поля ввода.

Он как раз не использует псевдо-класс :is(), а по старинке пишет через запятую.

.Input-text:not(:placeholder-shown) + .Input-label,
.Input-text:focus:not(:placeholder-shown) + .Input-label {
  /* здесь стили */
}

А сейчас я перепишу этот же код с помощью с псевдо-класса :is().

.Input-text:is(:not(:placeholder-shown), :focus:not(:placeholder-shown)) + .Input-label {
  /* здесь стили */
}

Псевдо-класс :is() позволяет соединить разные части селекторов, убрав дублирование. В нашем примере два селектора отличались частями :not(:placeholder-shown) и :focus:not(:placeholder-shown). Я взял их и засунул в псевдо-класс :is. Оставшиеся части селекторов, которые дублировались, написал только один раз.

Поскольку автор демонстрации использовал псевдо-класс :focus, я его оставил. Но он тоже устарел! По этой причине мне отдельно хочется доработать полученный селектор, заменив псевдо-класс :focus на :focus-visible.

.Input-text:is(:not(:placeholder-shown), :focus-visible:not(:placeholder-shown)) + .Input-label {
  /* здесь стили */
}

Отдельно предупрежу, что использовать элемент label после элемента input, как это делает автор демо, плохая идея. Я подробно рассказал о проблемах с этим решением в отдельной статье. Надеюсь, вы не пройдёте мимо!

А я оставлю здесь более корректный вариант плавающей метки, где элемент label находится перед полем.

.Input-label:has(+ .Input-text:is(:not(:placeholder-shown), :focus-visible:not(:placeholder-shown))) {
  /* здесь стили */
}

Медиа-функции min-width, max-width, min-height и max-height

CSS часто ругают. Говорят, что он супернеочевидный. Вот взять, например, медиа-выражения с использованием min-widthmax-widthmin-height и max-height.

@media (max-width: 1200px) {
  
  body {
    background-color: lightblue;
  }
}

Сколько себя помню, постоянно многие фронтендеры ругались на то, что непонятно, входит ли указанное значение медиа-функции (1200px) в диапазон или нет. Многие путались.

А ведь авторы спецификаций услышали эту проблему. Придумали более очевидный синтаксис, основанный на математических знаках, которые известны нам со школы.

В нашем примере отлично подойдёт знак <=.

@media (width <= 1200px) {
  
  body {
    background-color: lightblue;
  }
}

Знаете, в чём заключается проблема? Вот скажите мне, пожалуйста, вы используете этот синтаксис? Из ста опрошенных коллег только пятнадцать используют. Остальные пишут код по-старому. А зачем тогда надо было ругаться? Непонятно.

Заключение

Давайте подведём итог. В этой статье мы рассмотрели:

  • замену трюка с padding-top с помощью свойства aspect-ratio;

  • свойство inset со значением 0 для растягивания элемента на всё доступное пространство;

  • более простой синтаксис медиа-запросов, основанный на математических знаках;

  • псевдо-класс :is(), позволяющий сократить длину селекторов;

  • свойство isolation, решающее проблему с отрицательными значениями свойства z-index очевидным способом.

Это всё, что я хотел показать вам. Надеюсь, вы нашли тут что-то для себя, что захочется взять и применить в своих проектах. Может, даже вдохновились на что-то новое!

А теперь самое главное — ваша часть. Напишите, пожалуйста, в комментариях, что я упустил, или где вообще не прав. Я всё читаю и реально ценю вашу обратную связь.

Спасибо за чтение!

P. S. Помогаю больше узнать про CSS в своём ТГ-канале CSS isn't magic. Присоединяйтесь. Ссылка в профиле.

© 2025 ООО «МТ ФИНАНС»

Комментарии (7)


  1. danilovmy
    25.11.2025 09:14

    пример с is() ну такое... субъективное. Если label перенести выше, то почему тогда input не внутри? тогда все еще проще пишется.


    1. dom1n1k
      25.11.2025 09:14

      Мне тоже не очень нравится. Стало сложнее - нужно всматриваться в скобки, что куда вложено и тд. Традиционный вариант гораздо прозрачнее, пусть и длиннее по символам.

      Это не значит конечно, что where и is не нужны совсем - иногда нужны - но во многих случаях реально проще по старинке.


  1. nikolayshabalin
    25.11.2025 09:14

    А теперь range-синтаксис ещё можно в container quieries от стилей, так что теперь везде будет однородно

    @container style(--lightness < 50%) {
      color: white;
    }
    
    @container style(--lightness >= 50%) {
      color: black;
    }





  1. Dimox
    25.11.2025 09:14

    Про @media (width <= 1200px).

    Знаете, в чём заключается проблема? Вот скажите мне, пожалуйста, вы используете этот синтаксис?

    Я давно знаю про этот синтаксис и очень хотел бы его использовать, но...

    В одном из недавних проектов, который я дорабатывал, уже был этот синтаксис, но тестировщик сообщил, что в какой-то из версий iPhone (где-то 14-15, точно уже не помню) "поехала" верстка. Причина оказалась как раз в этом, и мне пришлось менять на старый вариант.

    Именно поэтому я все еще использую старый синтаксис. И наверняка я такой не один.


    1. arseniikrilo
      25.11.2025 09:14

      Потому что не все браузеры могут в этот новый синтаксис, и при использовании таких специфичных свойств стоит посмотреть поддерживаются они или нет, и может быть использовать фолбеки, что бы исключить возможность того, что где-то могут случиться глюки


    1. andreymal
      25.11.2025 09:14

      Я не использую ничего из современного CSS, потому что у меня до сих пор есть живые пользователи с Firefox 56


    1. nikolayshabalin
      25.11.2025 09:14

      Можно подключить postcss-плагин, чтобы писать в новом синтаксисе, а браузер получал в понятном ему =)