---
title: "Плавное появление блока в CSS: как анимировать display: none"
description: "Как на CSS плавно показать и скрыть блок с display: none: @starting-style и allow-discrete, рабочие примеры и поддержка браузеров."
url: https://penlab.space/blog/ru/css-transition-display-none
language: ru
section: "Статьи"
published: 2026-10-12T15:56:47Z
updated: 2026-10-12T15:56:47Z
translation: https://penlab.space/blog/css-transition-display-none
---

# Плавное появление блока в CSS: как анимировать display: none

Блок с display: none можно показать и скрыть плавно средствами CSS. Разбираем @starting-style и allow-discrete на рабочих примерах и смотрим, что с поддержкой в браузерах.

Плавное появление блока, скрытого через `display: none`, делается на CSS двумя приёмами. Правило `@starting-style` задаёт стиль, с которого начинается переход, а `display` с ключевым словом `allow-discrete` в списке переходов удерживает блок на экране, пока идёт скрытие. Появление работает во всех актуальных браузерах, плавное скрытие пока только в Chrome, Edge и Safari.

## Коротко

- Обычный `transition` не запускается, когда блок выходит из `display: none`: у браузера нет стиля, от которого считать переход.
- `@starting-style` задаёт этот стартовый стиль. Правило работает в Chrome и Edge 117, Firefox 129 и Safari 17.5.
- Строка `display 0.3s allow-discrete` в списке переходов удерживает блок на экране, пока идёт скрытие. Это работает в Chrome и Edge 117 и в Safari 18, в Firefox блок исчезает сразу.
- Для `popover` и `dialog` в список переходов добавляют ещё и `overlay`. Свойство понимают только Chrome и Edge.
- Высоту `auto` анимирует `interpolate-size: allow-keywords`, тоже только в Chrome и Edge.
- В браузере без поддержки ничего не ломается: блок показывается и скрывается без анимации.

## Почему transition не срабатывает с display: none

Переход в CSS считается между двумя стилями: тем, что был до изменения, и новым. Пока у блока стоит `display: none`, браузер его не отрисовывает, и стиля «до» у блока нет. По спецификации CSS Transitions переход для такого элемента не запускается, поэтому блок появляется сразу в конечном виде.

В примере у карточки прописан переход прозрачности, а класс `is-open` показывает её.

```css
.card {
  display: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.card.is-open {
  display: block;
  opacity: 1;
}
```

Этот код проверен в Chrome 154: через 100 миллисекунд после добавления класса прозрачность уже равна 1. Со скрытием причина другая. Свойство `display` дискретное, промежуточных значений между `block` и `none` у него нет. Значение `none` применяется в тот же момент, и анимировать прозрачность уже не на чем.

## Как плавно показать блок с помощью @starting-style

Правило `@starting-style` задаёт значения, с которых начинается переход, если до этого блок не отрисовывался. Мы записываем его внутри правила открытого состояния и указываем там стартовые значения.

```css
.card {
  display: none;
  opacity: 0;
  translate: 0 12px;
  transition:
    opacity 0.3s ease,
    translate 0.3s ease;
}

.card.is-open {
  display: block;
  opacity: 1;
  translate: 0 0;

  @starting-style {
    opacity: 0;
    translate: 0 12px;
  }
}
```

Когда класс `is-open` добавлен, браузер берёт значения из `@starting-style` как точку отсчёта и ведёт переход к `opacity: 1`, так что карточка за 0,3 секунды проявляется и сдвигается на место.

Правило `@starting-style` не получает дополнительного приоритета в каскаде. Если записать его отдельным блоком выше основного правила, основное правило перекроет стартовые значения. При вложенной записи, как в примере, следить за порядком не приходится.

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

## Как плавно скрыть блок с помощью allow-discrete

Чтобы скрытие шло плавно, в список переходов добавляют само свойство `display` с ключевым словом `allow-discrete`. Тогда блок остаётся видимым всю длительность перехода и получает `display: none` только в конце.

```css
.card {
  display: none;
  opacity: 0;
  translate: 0 12px;
  transition:
    opacity 0.3s ease,
    translate 0.3s ease,
    display 0.3s allow-discrete;
}

.card.is-open {
  display: block;
  opacity: 1;
  translate: 0 0;

  @starting-style {
    opacity: 0;
    translate: 0 12px;
  }
}
```

Слово `allow-discrete` разрешает переход для дискретного свойства. Обычно такие свойства переключаются на середине перехода, но для `display` в спецификации CSS Display сделано исключение: при переходе между `none` и любым другим значением блок считается видимым на всём протяжении.

Для скрытия `@starting-style` не нужен. Один из редакторов спецификаций CSS Таб Аткинс объяснял это в обсуждении рабочей группы (перевод наш):

> «`@starting-style` существует потому, что `display: none` затирает большинство стилей и не оставляет значения, от которого можно анимировать. При переходе в обратную сторону такой проблемы нет».
>
> Таб Аткинс, [обсуждение в репозитории CSSWG](https://github.com/w3c/csswg-drafts/issues/9500#issuecomment-4059705893), 14 марта 2026 года

Осталось переключать класс. Это делает короткий обработчик на JavaScript, сама анимация целиком описана в CSS.

```html
<button class="toggle" type="button">Показать или скрыть</button>
<div class="card">Этот блок появляется и исчезает плавно.</div>
```

```js
document.querySelector('.toggle').addEventListener('click', () => {
  document.querySelector('.card').classList.toggle('is-open');
});
```

Пример проверен в Chrome 154. Через 100 миллисекунд после клика прозрачность была примерно на середине пути и при появлении, и при скрытии, а `display: none` блок получил только после конца перехода.

[Живой пример: Плавное появление и скрытие блока с display: none](https://penlab.space/pen/g6MBdP8e)

Все три фрагмента можно вставить в [новый пен в Penlab](https://penlab.space/new): HTML, CSS и JS в свои панели. Превью обновится само, и длительность или сдвиг можно менять прямо там.

## Как анимировать popover и dialog

Всплывающий блок с атрибутом `popover` и модальное окно `dialog` анимируются по той же схеме, но в список переходов добавляется свойство `overlay`. Такие элементы браузер рисует в верхнем слое, поверх остальной страницы. При закрытии он сразу убирает элемент из верхнего слоя, и анимация обрывается, а переход `overlay` откладывает это до конца анимации.

Всплывающий блок открывается без JavaScript: кнопка связана с ним атрибутом `popovertarget`.

```html
<button popovertarget="hint" type="button">Открыть подсказку</button>
<div id="hint" popover>Подсказка открывается без JavaScript.</div>
```

```css
[popover] {
  opacity: 0;
  scale: 0.95;
  transition:
    opacity 0.25s ease,
    scale 0.25s ease,
    display 0.25s allow-discrete,
    overlay 0.25s allow-discrete;
}

[popover]:popover-open {
  opacity: 1;
  scale: 1;

  @starting-style {
    opacity: 0;
    scale: 0.95;
  }
}
```

[Живой пример: Popover с анимацией открытия и закрытия](https://penlab.space/pen/Q9D8vjW8)

Значение `overlay` задаёт только браузер, в своих стилях мы можем лишь включить его в `transition`. Сейчас свойство понимают Chrome и Edge. Остальные браузеры пропускают незнакомое имя в списке переходов, так что запись им не мешает.

Модальное окно `dialog` открывает метод `showModal()`. Само окно анимируется так же, как всплывающий блок, только открытое состояние задаёт атрибут `open`.

```html
<button class="open" type="button">Открыть окно</button>

<dialog class="modal">
  <p>Окно и затемнение появляются плавно.</p>
  <form method="dialog">
    <button>Закрыть</button>
  </form>
</dialog>
```

```css
.modal {
  opacity: 0;
  translate: 0 16px;
  transition:
    opacity 0.3s ease,
    translate 0.3s ease,
    display 0.3s allow-discrete,
    overlay 0.3s allow-discrete;
}

.modal[open] {
  opacity: 1;
  translate: 0 0;

  @starting-style {
    opacity: 0;
    translate: 0 16px;
  }
}
```

```js
document.querySelector('.open').addEventListener('click', () => {
  document.querySelector('.modal').showModal();
});
```

У окна есть ещё затемнение, псевдоэлемент `::backdrop`. Его стартовый стиль надёжнее записать отдельным блоком: вложенная запись для псевдоэлементов заработала в браузерах позже, чем сам `@starting-style`.

```css
.modal::backdrop {
  background-color: rgb(0 0 0 / 0);
  transition:
    background-color 0.3s ease,
    display 0.3s allow-discrete,
    overlay 0.3s allow-discrete;
}

.modal[open]::backdrop {
  background-color: rgb(0 0 0 / 0.5);
}

@starting-style {
  .modal[open]::backdrop {
    background-color: rgb(0 0 0 / 0);
  }
}
```

[Живой пример: Модальное окно dialog с анимацией и затемнением](https://penlab.space/pen/kLpOH58F)

Оба примера, всплывающий блок и окно с затемнением, проверены в Chrome 154 и отработали в обе стороны.

## Как анимировать высоту auto

Анимацию раскрытия по высоте включает свойство `interpolate-size: allow-keywords`. Без него браузер не умеет считать переход от числа к `auto`. Свойство наследуется, поэтому его ставят один раз на `:root`.

```css
:root {
  interpolate-size: allow-keywords;
}

.panel {
  height: 0;
  overflow: hidden;
  visibility: hidden;
  transition:
    height 0.3s ease,
    visibility 0.3s;
}

.panel.is-open {
  height: auto;
  visibility: visible;
}
```

[Живой пример: Раскрытие блока по высоте auto](https://penlab.space/pen/tTnNcPsC)

Мы прячем панель через `visibility: hidden`, потому что блок с нулевой высотой остаётся доступным с клавиатуры и для скринридера - программы, которая читает страницу вслух. Свойство `visibility` браузеры анимируют давно, и во время перехода панель остаётся видимой.

Сейчас `interpolate-size` работает только в Chrome и Edge с версии 129, а в Firefox и Safari панель откроется и закроется без анимации высоты.

## В каких браузерах это работает

На 11 октября 2026 года появление блока работает во всех актуальных браузерах, а плавное скрытие и остальные приёмы поддержаны не везде. Данные взяты из таблиц совместимости MDN и из баг-трекеров Firefox и Safari, где разработчики браузеров ведут учёт ошибок и задач. Текущие версии на эту дату: Chrome и Edge 155, Firefox 157, Safari 27.

- `@starting-style`: Chrome и Edge 117, Firefox 129, Safari 17.5. С 6 августа 2024 года у правила статус Baseline: так помечают возможности, которые работают в свежих версиях всех основных браузеров.
- `transition-behavior` и слово `allow-discrete`: Chrome и Edge 117, Firefox 129, Safari 17.4.
- Переход свойства `display`: Chrome и Edge 117, Safari 18. Firefox его не поддерживает, скрытие в нём происходит мгновенно.
- `overlay`: только Chrome и Edge 117.
- `interpolate-size`: только Chrome и Edge 129.
- Атрибут `popover`: Chrome и Edge 114, Firefox 125, Safari 17.

В Firefox работа над переходом `display` идёт: в версии 155 он появился, но включается настройкой `layout.css.display-animations.enabled`, которая по умолчанию выключена. В Safari нет `overlay`, поэтому при закрытии всплывающий блок или окно на время анимации теряет место в верхнем слое.

Примеры из статьи проверены только в Chrome 154. Поведение Firefox и Safari описано по документации и баг-трекерам, перед использованием в проекте его стоит проверить в этих браузерах.

## Когда достаточно opacity и visibility

Старый способ с `opacity` и `visibility` работает во всех браузерах в обе стороны. Он подходит, когда скрытый блок не мешает вёрстке, например выпадающему меню с абсолютным позиционированием: такое меню и так вынуто из потока страницы.

```css
.menu {
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.3s ease,
    visibility 0.3s;
}

.menu.is-open {
  opacity: 1;
  visibility: visible;
}
```

У остальных распространённых приёмов есть ограничения.

- Только `opacity: 0`. Невидимый блок продолжает перехватывать клики. С `pointer-events: none` мышью его уже не нажать, но фокус с клавиатуры всё равно попадает на невидимые ссылки и кнопки, и скринридер читает содержимое.
- `max-height` с запасом. Переход идёт до значения с запасом, поэтому видимая длительность не совпадает с заданной, а содержимое выше запаса обрезается.
- Сдвиг за пределы экрана. Блок остаётся доступным для фокуса и скринридера.
- JavaScript с `setTimeout` или событием `transitionend`. Кода становится больше, и событие не приходит, если переход прервали.

Обычный блок, скрытый через `visibility: hidden`, продолжает занимать место в потоке. Когда это мешает вёрстке, нужен `display: none` и способ из этой статьи.

## Как найти ошибку, если анимация не работает

Если переход не срабатывает, стоит проверить пять мест. Каждая ошибка из списка ниже встречается в вопросах на Stack Overflow или в баг-трекерах браузеров.

1. В списке переходов нет `display` с `allow-discrete`: появление плавное, скрытие мгновенное.
2. В правиле открытого состояния нет `@starting-style`: скрытие плавное, появление мгновенное.
3. `@starting-style` записан отдельным блоком выше основного правила, и основное правило перекрывает стартовые значения.
4. Слово `allow-discrete` задано отдельным свойством `transition-behavior`, и оно стоит перед сокращённой записью `transition`. Сокращённая запись сбрасывает его в значение по умолчанию.
5. От `@starting-style` ждут анимации скрытия. Правило действует только при появлении.

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

## Что попробовать в редакторе

Пример проще понять, если менять его руками. На пять шагов ниже уйдёт около десяти минут.

1. Откройте [редактор Penlab](https://penlab.space/new) и вставьте HTML, CSS и JS карточки из раздела про `allow-discrete`.
2. Уберите строку `display 0.3s allow-discrete`, а запятую в предыдущей строке замените на точку с запятой. Скрытие станет мгновенным.
3. Верните строку и запятую, а затем удалите блок `@starting-style`. Теперь рывком происходит появление.
4. Верните блок `@starting-style` и замените сдвиг на масштаб: `translate: 0 12px` на `scale: 0.95` в обоих местах, `translate: 0 0` на `scale: 1`, а в списке переходов `translate 0.3s ease` на `scale 0.3s ease`.
5. Увеличьте длительность до секунды во всех трёх строках списка переходов, иначе `display` переключится раньше, чем закончится анимация.

Сохранённым пеном можно поделиться ссылкой или вставить его на свою страницу, об этом мы писали в заметке [«Поделиться пеном и встроить его на свой сайт»](https://penlab.space/blog/ru/share-and-embed). Другие примеры участников собраны на [витрине](https://penlab.space/showcase).

## FAQ

### Можно ли сделать появление блока совсем без JavaScript?

Да, если состояние переключает сам браузер. Всплывающий блок с атрибутом `popover` открывается кнопкой с `popovertarget`, блок `details` раскрывается по клику, а класс можно заменить селектором `:has()` с флажком. JavaScript нужен, когда блок показывается по событию, которого в CSS нет.

### Нужен ли @starting-style для скрытия?

Нет. Правило задаёт стиль только для появления. При скрытии блок переходит к значениям своего базового правила, и для плавности достаточно `display` с `allow-discrete` в списке переходов.

### Можно ли анимировать появление блока через @keyframes?

Да, можно. В ключевых кадрах `display` поддерживается с Chrome 116, и `@starting-style` с `allow-discrete` для анимации не нужны. Переход удобнее тем, что при быстром повторном клике разворачивается с текущего места, а анимация начинается заново. В Firefox `display` в ключевых кадрах не анимируется.

### Как быть с пользователями, которые отключили анимацию?

Для пользователей с отключённой анимацией переход стоит убрать или сократить. Медиа-запрос `prefers-reduced-motion` поддерживают все актуальные браузеры.

```css
@media (prefers-reduced-motion: reduce) {
  .card {
    transition: none;
  }
}
```

### Что будет в браузере без поддержки?

Блок покажется и скроется без анимации. Правило `@starting-style` такой браузер пропускает, объявление `transition` с незнакомым словом `allow-discrete` отбрасывает целиком, а остальной CSS работает как обычно.

## Источники

- [CSS Transitions Level 2, раздел о @starting-style](https://drafts.csswg.org/css-transitions-2/#defining-before-change-style)
- [MDN: @starting-style](https://developer.mozilla.org/en-US/docs/Web/CSS/@starting-style)
- [Chrome for Developers: Four new CSS features for smooth entry and exit animations](https://developer.chrome.com/blog/entry-exit-animations)
- [CSS Display Module Level 4, анимация display](https://drafts.csswg.org/css-display-4/#display-animation)
- [MDN: transition-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior)
- [WebKit: возможности Safari 18.0](https://webkit.org/blog/15865/webkit-features-in-safari-18-0/)
- [CSS Transitions Level 1, запуск переходов](https://drafts.csswg.org/css-transitions-1/#starting)
- [MDN: overlay](https://developer.mozilla.org/en-US/docs/Web/CSS/overlay)
- [Chrome for Developers: Animate to height: auto](https://developer.chrome.com/docs/css-ui/animate-to-height-auto)
- [Bugzilla Mozilla: переход display в Firefox](https://bugzilla.mozilla.org/show_bug.cgi?id=1834877)
- [MDN: interpolate-size](https://developer.mozilla.org/en-US/docs/Web/CSS/interpolate-size)
- [web.dev: Baseline для анимаций появления](https://web.dev/blog/baseline-entry-animations)
- [WebKit Bugzilla: свойство overlay](https://bugs.webkit.org/show_bug.cgi?id=276727)
- [MDN: visibility](https://developer.mozilla.org/en-US/docs/Web/CSS/visibility)
- [Vispero: как способы скрытия влияют на доступность](https://vispero.com/resources/not-all-hidden-content-is-equal-how-different-hiding-methods-impact-accessibility/)
- [MDN: Using the Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API/Using)
- [CSS-Tricks: Using CSS Transitions on Auto Dimensions](https://css-tricks.com/using-css-transitions-auto-dimensions/)
