HTML глобальный атрибут popover

Определение и применение

Глобальный атрибут popover превращает HTML-элемент во всплывающую область: по умолчанию она скрыта, а после открытия отображается поверх обычного содержимого страницы. Например, кнопка может открывать небольшую справку, не переводя пользователя на другую страницу. Базовый показ и скрытие можно настроить прямо в HTML, без собственных обработчиков JavaScript.

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

Поддержка браузерами

Спецификация: multipage

Поддерживается во всех основных браузерах с 2024 года.

Отдельные значения (1) поддерживаются не везде — смотрите таблицу.

Показать браузеры
Совместимостьhtml.global_attributes.popover
ВозможностьКомпьютерыМобильные
ChromeEdgeFirefoxOperaSafariChrome AndroidFirefox for AndroidOpera AndroidSafari on iOSSamsung BrowserWebView AndroidWebView on iOS
popover✓114✓114✓125✓100✓17✓114✓125✓76✓17✓23.0✓114✓17
hint✓151✓151✓153✓135✓preview✓151✓153✓101×◐29.0✓151×

поддерживаетсяс ограниченияминет поддержкинет данных≤31 — не позднее версии 31

Поддержку popover и распознавание режима hint можно проверить отдельно:

JavaScript
const supportsPopover = 'popover' in HTMLElement.prototype;

const probe = document.createElement('div');
probe.setAttribute('popover', 'hint');
const recognizesHint = supportsPopover && probe.popover === 'hint';

Распознавание hint ещё не гарантирует полную поддержку его поведения. Если браузер поддерживает popover, но не hint, область работает как manual. Поэтому у примера подсказки есть явная кнопка закрытия.

Без поддержки popover содержимое остаётся видимым, а кнопки с popovertarget не работают. Можно сохранить обычный текст или использовать <details>. Не скрывайте важный текст через display: none без работающего способа показа.

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

HTML
<button type="button" popovertarget="delivery">
  Условия доставки
</button>

<section id="delivery" popover aria-labelledby="delivery-title">
  <h2 id="delivery-title">Доставка</h2>
  <p>Заказы доставляются по будням с 10 до 18 часов.</p>
  <button type="button" popovertarget="delivery" popovertargetaction="hide">
    Закрыть
  </button>
</section>

Атрибут popovertarget у <button> содержит значение id целевой области без символа #. Цель должна находиться в том же дереве документа, что и кнопка, и иметь атрибут popover. Такая связь также поддерживается у <input type="button">.

У кнопки явно указан type="button", чтобы внутри формы она не стала кнопкой отправки. Управляющую кнопку не помещают внутрь изначально скрытой панели: тогда до неё нельзя будет добраться. Дополнительная кнопка закрытия внутри панели, напротив, полезна. Попробуйте пример без JavaScript.

Значения атрибута

Режимы popover
ЗначениеПоведение
autoПоддерживает закрытие кликом снаружи и запросом закрытия, обычно клавишей Escape. Открытие другой независимой панели auto закрывает прежнюю; вложенные панели могут оставаться открытыми вместе.
manualЗакрывается явной командой кнопки или вызовом API. Клик снаружи и Escape автоматически его не закрывают. Можно открыть несколько независимых панелей.
hintРежим подсказок: открытие не закрывает панели auto, но закрывает другие hint, кроме предков в цепочке подсказок. Поддерживает клик снаружи и запрос закрытия. Совместимость проверяется отдельно.

Пустые записи popover и popover="" означают auto. При отсутствии атрибута элемент вообще не является всплывающей областью. Неизвестное значение обрабатывается как manual: например, popover="false" не отключает возможность. Это перечислимый атрибут: его значение задаёт режим работы всплывающей области.

Показ и закрытие из HTML

Действие кнопки задаёт popovertargetaction. Оно не меняет режим самой области: кнопка с hide может закрыть как auto, так и manual.

Значения popovertargetaction
ЗначениеДействие
toggleОткрыть закрытую область или закрыть открытую. Используется при отсутствии атрибута и при неизвестном значении.
showОткрыть область. Если она уже открыта, повторно переключать её не нужно.
hideЗакрыть область. Если она уже закрыта, она остаётся закрытой.

Слово manual не означает «обязательно написать JavaScript»: пример с явным закрытием целиком работает через HTML. Главное — предоставить пользователю понятный способ закрыть панель.

Автоматическое закрытие и вложенные панели

Закрытие при клике вне панели называют light dismiss — лёгким закрытием, без отдельной кнопки. Для auto браузер учитывает связи между всплывающими областями. Например, внутри панели фильтров можно открыть вложенное пояснение: открытие дочерней области не должно уничтожать её родителя.

Связь возникает, в частности, при вложении одного popover в другой или размещении управляющей кнопки внутри открытого popover. В примере вложенности используется явное вложение в DOM. Первое нажатие Escape закрывает дочернюю область, следующее — родительскую. Клик в родительской панели за пределами дочерней закрывает только дочернюю.

Режим hint предназначен для пояснений, которые могут появиться поверх открытой панели auto. Но сам атрибут не назначает реакцию на наведение или фокус и не создаёт role="tooltip". Способ открытия и семантика подсказки — отдельная задача. Более того, клик по внешней кнопке может закрыть auto как обычный клик снаружи; это не противоречит правилу, что само открытие hint не закрывает auto.

В примере hint кнопка подсказки расположена внутри основной панели, чтобы отдельно наблюдать взаимодействие режимов. В актуальной спецификации цепочки auto и hint учитываются раздельно; старые реализации hint могут иначе обрабатывать вложенные сочетания. Не переносите правила одного режима на другой.

Верхний слой и оформление

Открытая область попадает в верхний слой (top layer) — специальный слой отображения браузера над обычными контекстами наложения. Поэтому высокий z-index обычного блока не перекрывает popover, а overflow: hidden у предка не обрезает открытую панель. Среди элементов самого верхнего слоя важен порядок их добавления.

DOM при этом не меняется: элемент остаётся потомком своего контейнера, сохраняет наследуемые стили и путь распространения событий. Верхний слой не переносит элемент в конец body и не освобождает от инертности предка. Внутри iframe верхний слой ограничен документом фрейма: панель учебного примера не может выйти поверх всего сайта.

Браузер по умолчанию центрирует popover, задаёт рамку, фон и отступы. Связь с кнопкой сама по себе не размещает панель рядом с ней.

Открытое состояние: :popover-open

Псевдокласс :popover-open выбирает открытую всплывающую область. Закрытые popover скрыты браузером через display: none. Общее правило вроде [popover] { display: block; } может перебить скрытие и показать закрытую панель прямо в документе. Задавайте нужный display через :popover-open.

Например, включим сеточную раскладку только для открытой панели:

CSS
#delivery:popover-open {
  display: grid;
  gap: 0.5rem;
}

Фон и анимация

Псевдоэлемент ::backdrop позволяет оформить фон за открытой областью, но не делает интерфейс модальным. Для привязки панели к кнопке существует CSS Anchor Positioning; его поддержку проверяют отдельно. Синтаксис и варианты можно исследовать в инструменте якорного позиционирования.

Для плавного входа и выхода недостаточно анимировать только прозрачность: закрытие переключает display и удаляет элемент из верхнего слоя. Современный вариант использует переходы display и overlay с allow-discrete, а начальный стиль входа — @starting-style. Это отдельные возможности CSS с собственной совместимостью; без их поддержки панель должна продолжать открываться и закрываться мгновенно.

Фокус, клавиатура и доступность

popovertarget связывает кнопку с панелью: браузер учитывает эту связь при переходе фокуса и сообщает состояние раскрытия вспомогательным технологиям. Статический aria-expanded="false" добавлять не нужно.

  • Tab позволяет переходить к элементам панели и дальше по странице: фокус внутри не удерживается. autofocus задавайте только при необходимости сразу перейти к полю ввода.
  • Escape закрывает панели auto и hint. При программном управлении проверяйте возврат фокуса и не забирайте его у выбранного пользователем элемента.
  • Дайте кнопке понятное название, а панели — заголовок. Связать панель с заголовком можно через aria-labelledby, как в базовом примере.
  • Для обычного списка ссылок достаточно HTML-разметки. role="menu" требует отдельной клавиатурной навигации.

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

Управление через JavaScript

Методы вызывают у элемента с атрибутом popover, добавленного в документ:

JavaScript
const panel = document.querySelector('#delivery');

panel.showPopover();        // Показать.
panel.hidePopover();        // Скрыть.
panel.togglePopover();      // Переключить состояние.
panel.togglePopover(true);  // Показать, если закрыта.
panel.togglePopover(false); // Скрыть, если открыта.

const isOpen = panel.matches(':popover-open'); // Открыта ли панель.

Свойство panel.popover хранит режим ('auto', 'manual', 'hint' или null при отсутствии атрибута), а не состояние видимости. Его изменение само по себе не открывает панель.

Для проверки видимости используйте matches(':popover-open'), как в примере: возвращаемое значение togglePopover() различается в старых и новых реализациях.

Один элемент нельзя одновременно открыть как popover и модальный диалог или перевести в полноэкранный режим: такие вызовы могут привести к исключению.

События beforetoggle и toggle

Событие beforetoggle приходит перед изменением видимости, toggle — после него. Их свойства oldState и newState содержат строки 'open' или 'closed'. Обработчики регистрируют на самой панели:

JavaScript
panel.addEventListener('beforetoggle', (event) => {
  if (event.newState === 'open') {
    // Подготовить содержимое до показа.
  }
});

panel.addEventListener('toggle', (event) => {
  console.log(event.oldState, event.newState);
});

Открытие можно отменить вызовом event.preventDefault() в beforetoggle. Закрытие через это событие не отменяется. Событие toggle тоже не отменяемое; несколько быстрых переключений могут объединиться в одно уведомление. Поэтому оно подходит для отражения итогового состояния, но не для подсчёта каждого вызова метода.

Не отслеживайте состояние только обработчиком клика управляющей кнопки: панель может закрыться через Escape, клик снаружи или открытие другой панели. В примере API и событий сообщение обновляется через toggle, а beforetoggle демонстрирует отмену открытия.

Типичные ошибки

  • popovertarget="#help" вместо popovertarget="help", повторяющиеся id или цель без popover.
  • Ожидание, что popover="manual" закроется по Escape без отдельного обработчика.
  • Добавление hidden к popover: это другой механизм скрытия, который может помешать показу через API.
  • Изменение только display вместо использования кнопки или API: визуальный показ не переводит элемент в открытое состояние popover.
  • Ожидание автоматического размещения у кнопки, автоматического наведения у hint или готовой клавиатурной модели ARIA-меню.
  • Применение aria-modal="true" к немодальной панели: атрибут сообщает неверную модель взаимодействия и не делает фон недоступным.

Спецификация

HTML Living Standard: popover.

Примеры, попробуйте сами

Примеры используют нативный Popover API браузера. В HTML-примерах без его поддержки панели будут обычными видимыми блоками. Для проверки Escape сначала установите фокус внутри результата.

Панель без JavaScript

Откройте справку кнопкой, затем закройте её повторным нажатием, кликом снаружи или Escape. Клавиша Tab позволяет перейти к кнопке внутри панели. Измените popovertarget и сравните его с id цели.

index.html
HTML
<button type="button" popovertarget="delivery">Условия доставки</button>
<p><label>Поле вне панели <input placeholder="Фон остаётся доступен"></label></p>

<section id="delivery" popover aria-labelledby="delivery-title">
  <h2 id="delivery-title">Доставка</h2>
  <p>Заказы доставляются по будням с 10 до 18 часов.</p>
  <button type="button" popovertarget="delivery" popovertargetaction="hide">Закрыть</button>
</section>

Явное закрытие manual

Откройте панель и попробуйте кликнуть снаружи или нажать Escape: manual останется открытым. Закройте его кнопкой с popovertargetaction="hide". Замените режим на auto и сравните поведение.

index.html
HTML
<button type="button" popovertarget="note" popovertargetaction="show">Показать памятку</button>
<p><label>Поле вне панели <input placeholder="Можно продолжать работу"></label></p>

<section id="note" popover="manual" aria-labelledby="note-title">
  <h2 id="note-title">Памятка</h2>
  <p>Перед отправкой заказа проверьте адрес доставки.</p>
  <button type="button" popovertarget="note" popovertargetaction="hide">Закрыть памятку</button>
</section>

Вложенные и независимые панели

Откройте фильтры, затем пояснение внутри них: обе панели останутся открыты. Закрывайте их клавишей Escape по очереди. Снова откройте фильтры и нажмите внешнюю кнопку доставки: независимая панель auto заменит фильтры. В style.css измените положение панелей.

index.html
HTML
<button type="button" popovertarget="filters">Фильтры</button>
<button type="button" popovertarget="delivery">Доставка</button>

<section id="filters" popover aria-labelledby="filters-title">
  <h2 id="filters-title">Фильтры каталога</h2>
  <p><label><input type="checkbox"> Только в наличии</label></p>
  <button type="button" popovertarget="explanation">Что значит «в наличии»?</button>
  <button type="button" popovertarget="filters" popovertargetaction="hide">Закрыть фильтры</button>

  <section id="explanation" popover aria-labelledby="explanation-title">
    <h2 id="explanation-title">Наличие товара</h2>
    <p>Товар можно получить со склада без предзаказа.</p>
    <button type="button" popovertarget="explanation" popovertargetaction="hide">Закрыть пояснение</button>
  </section>
</section>

<section id="delivery" popover aria-labelledby="delivery-title">
  <h2 id="delivery-title">Доставка</h2>
  <p>Эта панель независима от фильтров.</p>
  <button type="button" popovertarget="delivery" popovertargetaction="hide">Закрыть доставку</button>
</section>
style.css
CSS
/* Разводим панели по высоте, чтобы видеть родительскую и дочернюю одновременно. */
[popover] {
  margin: 0;
  max-width: calc(100vw - 4rem);
}

#filters,
#delivery {
  inset: 4rem auto auto 1rem;
}

#explanation {
  inset: 13rem auto auto 2rem;
}

Подсказка поверх auto

Откройте настройки и подсказку внутри них. В браузере с поддержкой hint подсказка не закрывает auto. Попробуйте Escape. Если hint не поддерживается, он работает как manual; используйте явную кнопку закрытия. Частичная реализация может отличаться в сложных сценариях.

index.html
HTML
<button type="button" popovertarget="settings">Настройки</button>

<section id="settings" popover="auto" aria-labelledby="settings-title">
  <h2 id="settings-title">Настройки уведомлений</h2>
  <p><label><input type="checkbox"> Присылать новости</label></p>
  <button type="button" popovertarget="hint">О частоте писем</button>
  <button type="button" popovertarget="settings" popovertargetaction="hide">Закрыть настройки</button>

  <section id="hint" popover="hint" aria-labelledby="hint-title">
    <h2 id="hint-title">Частота писем</h2>
    <p>Не чаще одного письма в неделю.</p>
    <button type="button" popovertarget="hint" popovertargetaction="hide">Закрыть подсказку</button>
  </section>
</section>
style.css
CSS
/* Размещение позволяет наблюдать обе панели, не перекрывая кнопку подсказки. */
[popover] {
  margin: 0;
  max-width: calc(100vw - 4rem);
}

#settings {
  inset: 3rem auto auto 1rem;
}

#hint {
  inset: 13rem auto auto 2rem;
}

Методы и события Popover API

Нажмите ▶. Откройте и закройте панель кнопками API; проверьте сообщение после Escape и клика снаружи. Включите запрет открытия: beforetoggle отменит показ через preventDefault(). Перезапустите пример для возврата в начальное состояние.

index.html
HTML
<p><label><input id="block" type="checkbox"> Запретить открытие</label></p>
<button id="show" type="button" popovertarget="panel" disabled>Показать через API</button>
<button id="toggle" type="button" popovertarget="panel" disabled>Переключить через API</button>
<p id="status" role="status">Запустите пример.</p>

<section id="panel" popover aria-labelledby="panel-title">
  <h2 id="panel-title">Панель с событиями</h2>
  <p>Состояние отслеживается независимо от способа закрытия.</p>
  <button id="hide" type="button" autofocus>Закрыть через API</button>
</section>
main.js
JavaScript
const panel = document.querySelector('#panel');
const show = document.querySelector('#show');
const toggle = document.querySelector('#toggle');
const hide = document.querySelector('#hide');
const block = document.querySelector('#block');
const statusMessage = document.querySelector('#status');
let opener = null;

if (!('popover' in HTMLElement.prototype)) {
  statusMessage.textContent = 'Popover API не поддерживается. Содержимое показано обычным разделом.';
  hide.disabled = true;
} else {
  show.disabled = false;
  toggle.disabled = false;
  statusMessage.textContent = 'Панель закрыта.';

  // popovertarget связывает внешние кнопки с панелью: нажатие не считается
  // кликом снаружи. Отменяем встроенное действие, поскольку вызываем API сами.
  show.addEventListener('click', (event) => {
    event.preventDefault();
    opener = show;
    panel.showPopover();
  });

  toggle.addEventListener('click', (event) => {
    event.preventDefault();
    opener = toggle;
    panel.togglePopover();
  });

  hide.addEventListener('click', () => {
    // При явном закрытии возвращаем фокус к последней кнопке открытия.
    // Клик снаружи не должен отбирать фокус у выбранного пользователем поля.
    panel.hidePopover();
    if (opener) {
      opener.focus();
    }
  });

  panel.addEventListener('beforetoggle', (event) => {
    if (event.newState === 'open' && block.checked) {
      event.preventDefault();
      statusMessage.textContent = 'Открытие отменено в beforetoggle.';
    }
  });

  panel.addEventListener('toggle', (event) => {
    statusMessage.textContent = event.newState === 'open' ? 'Панель открыта.' : 'Панель закрыта.';
  });
}