Если присмотреться к большинству CSS-файлов на любом современном сайте, то рядом с обычными селекторами обязательно встретятся записи, начинающиеся с символа @: медиа-запросы, импорт сторонних стилей, описания анимаций. Всё это — так называемые at-правила (CSS at-rules). Они задают поведение всего стиля или его части и работают на уровне выше, чем привычные селекторы.

В этой статье разберём пять самых востребованных at-правил — @import, @media, @supports, @keyframes и @layer: какую задачу решает каждое, как пишется и какие нюансы стоит держать в голове. В конце коротко перечислим остальные at-правила, чтобы было, куда копать дальше.

Что такое at-правило

At-правило — это инструкция для браузера, которая начинается с @ и идентификатора. По синтаксису они делятся на две группы:

  • однострочные — завершаются точкой с запятой и обычно располагаются в начале файла:
    @identifier "значение";
  • блочные — содержат фигурные скобки с CSS-правилами или вложенными at-правилами, часто с условием:
    @identifier условие {
      /* CSS-правила или вложенные at-правила */
    }

Полный список существующих at-правил есть на MDN. Мы остановимся на тех, которые встречаются в работе чаще всего.

Правило @import — подключение внешних стилей

Правило @import позволяет подгрузить в текущий CSS-файл другой CSS-файл. Удобно, когда стили проекта разбиты на модули — типографика, сетка, компоненты — и хочется собрать их в одну точку входа без подключения нескольких <link> в HTML.

@import url('typography.css');
@import url('grid.css');
@import url('components/buttons.css');

Пример сборки. Допустим, есть три файла: heading.css с правилами для заголовков, paragraph.css для абзацев и общий main.css, который их объединяет.

/* heading.css */
h1 {
  text-decoration: underline;
  color: crimson;
}
/* paragraph.css */
p {
  color: midnightblue;
}
/* main.css */
@import url('heading.css');
@import url('paragraph.css');

body {
  font-family: 'Inter', sans-serif;
  margin: 24px;
}

В HTML подключаем только main.css, а он сам подтянет всё остальное.

Важный момент: @import-правила должны быть в самом верху файла, до обычных селекторов и блочных at-правил. Браузер скачивает импортируемые стили последовательно — сначала загрузится главный файл, затем по очереди каждый импорт. На больших проектах с десятками импортов это бьёт по производительности, поэтому в продакшене предпочитают объединять файлы на этапе сборки (Vite, Webpack), а @import оставляют для разработки или нечастых случаев. Поддерживается во всех браузерах.

Правило @media — адаптивность

Правило @media, более известное как медиа-запрос, применяет блок стилей только при выполнении заданного условия — например, ширина экрана меньше определённого значения или у устройства тёмная тема. Это базовый инструмент адаптивной вёрстки.

@media (медиа-условие) {
  /* стили, которые применятся при выполнении условия */
}

Простой пример: галерея карточек, которая на широких экранах выстраивается в ряд, а на узких — в колонку.

.product-list {
  display: flex;
  flex-direction: row;
  gap: 16px;
  width: 80%;
  margin: 0 auto;
}

.product-card {
  flex: 1;
  background: teal;
  color: white;
  padding: 24px;
  text-align: center;
}

/* до 600px - перестраиваем в колонку */
@media (max-width: 600px) {
  .product-list {
    flex-direction: column;
    width: 100%;
  }
  .product-card {
    background: slategray;
  }
}

/* до 360px - сужаем дополнительно */
@media (max-width: 360px) {
  .product-card {
    background: darkslategray;
    color: lightyellow;
  }
}

Правило большого пальца: при max-width-запросах их пишут от больших значений к меньшим, при min-width — наоборот, от меньших к большим. Иначе более общий запрос перепишет более узкий, и стили не сработают.

Кроме ширины экрана, @media умеет реагировать на ориентацию, плотность пикселей, предпочтения пользователя по цветовой схеме и анимациям. Подробно про последний случай мы разбирали в статье про prefers-reduced-motion.

Правило @supports — проверка возможностей браузера

Когда новая CSS-фича только-только появилась, важно дать резервный вариант для браузеров, которые её ещё не понимают. Раньше для этого использовали JavaScript-проверки и сторонние библиотеки вроде Modernizr, теперь же есть встроенный механизм — @supports. Он применяет блок стилей, только если браузер поддерживает указанное свойство со значением.

@supports (свойство: значение) {
  /* стили, которые применятся, если фича поддерживается */
}

Например, проверим поддержку display: grid и зададим сетку, только если она доступна:

@supports (display: grid) {
  .article-feed {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
    color: navy;
  }
}

Можно проверять и совсем свежие свойства — в условии браузер просто вернёт false, если не понимает запись:

@supports (scroll-timeline: auto) {
  .progress-bar {
    scroll-timeline: auto;
    color: forestgreen;
  }
}

Конструкции not, and и or позволяют комбинировать проверки. Перед тем как писать резервный сценарий, имеет смысл свериться с caniuse.com — если фича уже у 99% пользователей, оборачивать её в @supports просто не нужно.

Правило @keyframes — описание анимации

Правило @keyframes описывает сценарий анимации: какие свойства и как меняются в течение цикла. Само по себе оно ничего не запускает — чтобы сценарий заиграл, его нужно навесить на элемент через animation-свойства. Подробному разбору самих свойств у нас посвящена отдельная статья про CSS-анимации, а здесь разберём только @keyframes.

Простой случай с двумя точками описывают через from и to:

@keyframes имя-анимации {
  from { /* начальное состояние */ }
  to   { /* конечное состояние */ }
}

Если нужно несколько промежуточных кадров, используется процентная запись:

@keyframes имя-анимации {
  0%   { /* стартовый кадр */ }
  50%  { /* середина */ }
  100% { /* финальный кадр */ }
}

Живой пример: квадрат пульсирует и плавно меняет градиент по бесконечному циклу.

.pulse-card {
  width: 120px;
  height: 120px;
  border-radius: 16px;
  animation: pulse 2.4s ease-in-out infinite;
}

@keyframes pulse {
  0% {
    transform: scale(1);
    background: linear-gradient(135deg, #6a11cb, #2575fc);
    box-shadow: 0 0 0 rgba(106, 17, 203, 0.4);
  }
  50% {
    transform: scale(1.08);
    background: linear-gradient(135deg, #ff6a00, #ee0979);
    box-shadow: 0 12px 32px rgba(238, 9, 121, 0.4);
  }
  100% {
    transform: scale(1);
    background: linear-gradient(135deg, #6a11cb, #2575fc);
    box-shadow: 0 0 0 rgba(106, 17, 203, 0.4);
  }
}

Полный интерактивный пример для @keyframes:

Правило @layer — управление каскадом

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

Правило @layer (каскадные слои) предлагает другой подход: разложить стили по именованным слоям, а порядок этих слоёв задать явно. Слой, объявленный позже, имеет приоритет над предыдущими — независимо от специфичности селекторов внутри.

@layer имя-слоя {
  /* CSS-правила внутри слоя */
}

Порядок слоёв задаётся отдельной директивой в самом начале файла:

@layer имя-слоя-1, имя-слоя-2, имя-слоя-3;

Здесь имя-слоя-3 — самый приоритетный, имя-слоя-1 — самый низкий.

Разберём на примере. Есть кнопка .theme-button и есть ссылка внутри основной части страницы .main-content a, которая по правилам типографики должна быть красной с подчёркиванием. Но кнопка тоже может оказаться внутри .main-content — и тогда селектор .main-content a (специфичность 0,1,1) перебивает .theme-button (0,1,0). Без слоёв:

/* типографика */
.main-content a {
  color: crimson;
  text-decoration: underline;
}

/* компонент */
.theme-button {
  background: royalblue;
  color: #fff;
  padding: 8px 14px;
  border-radius: 8px;
  text-decoration: none;
}

Кнопка внутри .main-content приедет красной и с подчёркиванием. Со слоями проблема решается без переписывания селекторов:

@layer typography, component;

@layer typography {
  .main-content a {
    color: crimson;
    text-decoration: underline;
  }
}

@layer component {
  .theme-button {
    background: royalblue;
    color: #fff;
    padding: 8px 14px;
    border-radius: 8px;
    text-decoration: none;
  }
}

Теперь стили из слоя component побеждают стили из typography, даже несмотря на более высокую специфичность последних. Если завтра нужно поменять приоритет — достаточно переставить имена в директиве, не трогая сами стили.

Поддержка браузерами
chrome
Chrome
99
firefox
Firefox
97
internet explorer
IE
 
edge
Edge
99
safari
Safari
15.4
opera
Opera
86

Что ещё посмотреть

Помимо разобранных выше, в спецификации есть ещё несколько at-правил, которые встречаются реже, но в нужный момент сильно выручают:

  • @font-face — подключение пользовательских шрифтов с указанием формата, диапазона символов и стратегии загрузки;
  • @container — контейнерные запросы, реагирующие на размер родителя, а не всего окна. Подробный разбор есть в отдельной статье;
  • @property — типизированные кастомные свойства с возможностью анимации;
  • @scope — ограничение области действия селектора частью DOM-дерева (полезно для виджетов и компонентов);
  • @page — стили для печати и PDF-экспорта;
  • @counter-style — кастомные маркеры для списков.

Итог

At-правила — это не «экзотика», а основной инструмент тех мест в CSS, где обычных селекторов не хватает: подключение модулей, реакция на устройство, фича-детекция, анимации, управление каскадом. Понимание базовой пятёрки — @import, @media, @supports, @keyframes, @layer — уже закрывает 90% задач, в которых вообще встречается символ @. Остальные правила удобно держать в фоне и доставать, когда задача под них точно подходит.