CSS Nesting

Нативная вложенность CSS позволяет писать дочерние правила внутри родительских, используя символ & для ссылки на родительский селектор — аналогично Sass/SCSS, но встроено в браузер.

Зачем нужно

Вложенность делает CSS более читаемым и организованным, группируя связанные правила. Раньше это было возможно только в препроцессорах (Sass, Less). Теперь — нативная возможность браузеров, не требующая сборки.

Где используется

  • Стилизация компонентов (все стили компонента в одном блоке)
  • Состояния элементов (:hover, :focus, :active)
  • Медиа-запросы внутри компонента
  • Псевдоэлементы (::before, ::after)
  • Модификаторы и варианты компонентов

Предпосылки

Базовый синтаксис

Вложенные правила

.card {
  padding: 20px;
  border-radius: 12px;
  background: white;

  /* Вложенный селектор — &  можно опустить перед .class, :pseudo, ::pseudo */
  .title {
    font-size: 1.25rem;
    font-weight: 600;
    margin-bottom: 8px;
  }

  .body {
    color: #666;
    line-height: 1.6;
  }

  .footer {
    margin-top: 16px;
    display: flex;
    justify-content: flex-end;
  }
}

/* Эквивалент без вложенности: */
.card { padding: 20px; border-radius: 12px; background: white; }
.card .title { font-size: 1.25rem; font-weight: 600; margin-bottom: 8px; }
.card .body { color: #666; line-height: 1.6; }
.card .footer { margin-top: 16px; display: flex; justify-content: flex-end; }

Символ & — ссылка на родителя

.button {
  background: #007bff;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 6px;
  cursor: pointer;

  /* & = .button */
  &:hover {
    background: #0056b3;
  }

  &:active {
    transform: scale(0.98);
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }

  /* &.modifier = .button.modifier (на том же элементе) */
  &.primary {
    background: #007bff;
  }

  &.danger {
    background: #dc3545;
  }
}

& для составных селекторов

.nav {
  display: flex;
  gap: 8px;

  /* .nav-item — & используется для объединения */
  &-item {
    padding: 8px 16px;
  }
  /* ВНИМАНИЕ: это НЕ работает как в Sass!
     Результат: .nav-item (НЕ .nav .nav-item) — только если & в начале
     На самом деле в CSS nesting &-item = :is(.nav)-item = невалидно!
     Для BEM используйте полный класс: */

  .nav-item {
    padding: 8px 16px;
  }
}

В отличие от Sass, нативный CSS Nesting не поддерживает конкатенацию &-suffix. Для BEM-нотации пишите полные классы.

Вложенные медиа-запросы

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 24px;

  @media (max-width: 768px) {
    padding: 0 16px;
  }
}

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;

  @media (max-width: 1024px) {
    grid-template-columns: repeat(2, 1fr);
  }

  @media (max-width: 640px) {
    grid-template-columns: 1fr;
    gap: 16px;
  }
}

Вложенные псевдоэлементы

.link {
  color: #007bff;
  text-decoration: none;

  &::before {
    content: "";
    display: inline-block;
    width: 16px;
    height: 16px;
    margin-right: 4px;
    background: url("icon.svg") center/contain no-repeat;
  }

  &::after {
    content: " \2192"; /* стрелка → */
  }

  &:hover {
    text-decoration: underline;

    &::after {
      content: " \2192\2192"; /* двойная стрелка */
    }
  }
}

Глубокая вложенность

.page {
  .header {
    display: flex;
    align-items: center;

    .logo {
      font-size: 1.5rem;
      font-weight: bold;
    }

    .nav {
      display: flex;
      gap: 16px;

      a {
        color: inherit;
        text-decoration: none;

        &:hover {
          color: #007bff;
        }
      }
    }
  }
}

Не злоупотребляйте глубокой вложенностью. 3 уровня максимум — иначе код становится нечитаемым и повышается специфичность.

Сравнение с Sass

Возможность CSS Nesting Sass
Базовая вложенность Да Да
& для родителя Да Да
&-suffix (BEM) Нет Да
@media внутри Да Да
Переменные CSS custom properties $variables
Требует сборки Нет Да
Работа в рантайме Да Нет (компилируется)

Полный пример компонента

.card {
  --card-padding: 20px;
  --card-radius: 12px;

  background: white;
  border-radius: var(--card-radius);
  padding: var(--card-padding);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  transition: box-shadow 200ms ease;

  &:hover {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  }

  .image {
    width: 100%;
    aspect-ratio: 16/9;
    object-fit: cover;
    border-radius: calc(var(--card-radius) - 4px);
    margin-bottom: 12px;
  }

  .title {
    font-size: 1.125rem;
    font-weight: 600;
    margin-bottom: 8px;
  }

  .description {
    color: #666;
    font-size: 0.9rem;
    line-height: 1.5;
  }

  .actions {
    display: flex;
    gap: 8px;
    margin-top: 16px;
  }

  /* Варианты */
  &.featured {
    border: 2px solid #ffd700;
  }

  &.compact {
    --card-padding: 12px;
  }

  /* Адаптивность */
  @media (max-width: 640px) {
    --card-padding: 16px;

    .actions {
      flex-direction: column;
    }
  }
}

Частые ошибки

  1. Попытка конкатенации &-suffix — не работает как в Sass:
    .block {
      &-element { } /* Невалидно в CSS nesting! */
    }
    
  2. Слишком глубокая вложенность — повышает специфичность и ухудшает читаемость
  3. Вложенность тегов без & — для тегов нужно начинать с &:
    .card {
      /* Для тегов нужно & или начальный комбинатор */
      & p { color: #666; }
      /* или */
      > p { color: #666; }
    }
    
  4. Забыли про браузерную поддержку — проверьте Can I Use для целевой аудитории

Практика

  • Переписать плоский CSS компонента с вложенностью
  • Добавить :hover, :focus, :active через &
  • Вложить медиа-запросы внутрь компонента
  • Создать компонент с вариантами через &.modifier
  • Ограничить вложенность до 3 уровней

Связанные темы

Ресурсы