Интерактив без скрипта: details, dialog и inert
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Вопросы, ответы на которые раскрываются по щелчку, окно «Черновик сохранён» с кнопкой «Понятно», часть страницы, замороженная на время оплаты, — всё это привыкли писать скриптом, и обычно скрипт этот живёт в каждом проекте заново. Но в HTML для таких вещей есть готовые элементы: <details>, <dialog> и атрибут inert. Соберём раскрывающийся ответ сначала из заголовка с абзацем и посчитаем, скольких заплат он требует, а потом встретим <details>. Закроем окно формой без единой строки скрипта, выключим целую область одним атрибутом — и отделим то, что уже работает везде, от того, что пока требует скрипта или появилось слишком недавно.
У магазина есть страница «Вопросы и ответы»: доставка, оплата, возврат, и под каждым вопросом абзац ответа. Пока вопросов три, их пишут подряд. Когда их тридцать, страница вытягивается на несколько экранов, и ответы хочется спрятать: пусть человек видит список вопросов и открывает только тот, что ему нужен.
Соберём такой вопрос из подручного: заголовок <h3> и абзац под ним. Правило .answer { display: none; } прячет ответ — точка в адресе означает класс, а display: none убирает элемент со страницы целиком: он не рисуется и места не занимает. Осталось научить вопрос открывать ответ. Проверка щёлкнет по вопросу, спросит у ответа его display и попробует поставить на вопрос фокус — отметку «клавиатура сейчас здесь». Прогноз: изменится ли display после щелчка и примет ли вопрос фокус? Ответ — в разборе после запуска.
<h3> нет: ответ как был none, так и остался. Фокус заголовок тоже не принял, значит, Tab его пропустит.Чтобы самоделка заработала, пришлось бы писать скрипт, который по щелчку меняет display, давать вопросу tabindex, учить его Enter и Пробелу и отдельно сообщать программе чтения с экрана, открыт ответ или закрыт. Четыре заплаты, и каждую легко забыть — молча, потому что страница при этом продолжает выглядеть правильно.
Каждая из четырёх заплат забывается молча. Не поставили tabindex — человек с клавиатуры до вопроса не дойдёт, а с мышью всё работает. Не научили Enter и Пробелу — то же самое: на проверке мышью не всплывёт. Не сообщили программе чтения, открыт ответ или закрыт, — человек услышит «Доставка?» и не узнает, что под ним есть ответ. Именно поэтому готовые элементы выигрывают не объёмом кода, а тем, что забыть в них нечего.
В HTML такой элемент уже есть. <details> — блок, который раскрывается, <summary> первым внутри него — вопрос, который видно всегда. Всё остальное внутри <details> — ответ: браузер показывает его, только когда блок открыт. Атрибут open в разметке открывает блок сразу.
Ниже два таких блока с классом faq: первый закрыт, второй открыт. Пробел в адресе значит «внутри», поэтому .faq summary — это вопросы внутри блоков. Высоту вопроса и ответа задал стиль, 30 и 40 пикселей, чтобы числа не зависели от шрифта, а margin: 0 убрал отступы, которые браузер ставит абзацу сам. Прогноз: какой высоты окажутся блоки до щелчка по вопросу и после него? Числа назовёт разбор.
Скрипта в разметке нет: щелчок по <summary> браузер обрабатывает сам и ставит блоку атрибут open, а следующий щелчок атрибут снимает. Треугольник перед вопросом тоже рисует браузер — у закрытого блока он смотрит вправо, у открытого вниз, — и перерисовывает его сам. Ровно та работа, которую в самоделке делали бы вы.
Клавиатуру прибор не заменит: нажатие, отправленное из кода, действия браузера не вызывает. Поэтому руками. Щёлкните в рамке по вопросу «Доставка?» — ответ свернётся, а фокус встанет на вопрос. Нажмите Enter — ответ развернётся. Пробел свернёт его снова.
| Что умеет | <h3> и абзац | <details> и <summary> |
|---|---|---|
| Прячет ответ | да, стилем | да |
| Открывает ответ щелчком | нет | да |
| Вопрос принимает фокус | нет | да |
| Открывает ответ по Enter и Пробелу | нет | да |
| Хранит состояние в разметке | нет | да, атрибутом open |
| Сообщает программе чтения, открыт ли ответ | нет | да |
Последняя строка таблицы прибором не измеряется, но её видно в дереве доступности — карте страницы, которую браузер отдаёт программам чтения: вопрос там значится раскрывающимся, с пометкой, свёрнут он или развёрнут. У <details> и <summary> статус Baseline Widely available с 15 июля 2022 года — пишите.
Проверять, открыт ли блок, так и тянет по ответу: закрыт — значит, у абзаца нет высоты. Возьмём ту же разметку, уже без щелчка, и спросим про сами абзацы — .faq p, то есть абзацы внутри блоков, — а заодно про свойство open у самих блоков: замер умеет читать не только стили, но и свойства элементов. Прогноз: какой высоты окажется абзац закрытого ответа?
display у него block, как у открытого. В третьей строке open — false у первого блока и true у второго: свойство повторяет атрибут — где в разметке стоит open, там и true.Закрытый <details> не удаляет ответ и не меняет ему стиль: браузер держит содержимое в отдельном отсеке, который не рисует и которому не отводит места. Абзац в отсеке сохраняет свои размеры, а высота всего блока складывается из одного вопроса. Отсюда правило: об открытости спрашивают сам <details> — честно отвечают его высота и свойство open.
Пока блок закрыт, ссылки и кнопки в его ответе фокус не принимают, и Tab до них не дойдёт.
Второе частое поведение — окно поверх страницы: «Черновик сохранён», «Точно удалить?», выбор города. Для него есть <dialog>. Без атрибута open окно спрятано, с атрибутом — показано. Ниже два одинаковых окна с кнопкой, открыто только второе; проверка спросит у обоих display и попробует поставить фокус на их кнопки. Прогноз: каким окажется display у каждого окна и примут ли фокус кнопки?
display: none — так же, как ответ закрытого <details>, — и его кнопка выпала из очереди Tab. Открытое окно — блок, и кнопка в нём фокус принимает.В рамке примера открытое окно стоит посередине по ширине, обведённое чёрной линией, — это стили браузера.
Открыть окно разметкой просто. Интереснее закрыть его без скрипта. Для этого внутрь кладут форму с method="dialog": такая форма ничего не отправляет на сервер, а закрывает окно, в котором стоит. Значение value нажатой кнопки браузер записывает в свойство окна returnValue, и потом видно, какой именно кнопкой окно закрыли. Прогноз для двух строк разбора: открыто ли окно после щелчка по кнопке и что окажется в returnValue?
returnValue — ok. Строки «форма отправлена» нет: отправки не было, форма только закрыла окно.Если кнопок две — «Удалить» с value="delete" и «Оставить» с value="keep", — returnValue скажет, какую выбрали. Прочитать ответ и что-то с ним сделать — уже работа скрипта, но открывается и закрывается окно без него. У <dialog> статус Baseline Widely available с 14 сентября 2024 года.
Окна привыкли закрывать и клавишей Esc. Проверьте руками: уберите из примера строку с press и запустите его снова. Щёлкните по окну рядом с кнопкой и нажмите Esc — окно останется на месте. Щелчок по «Понятно» его закроет.
По умолчанию Esc закрывает только модальное окно — такое, которое на время отключает всю остальную страницу. Модальным окно делает метод showModal(), а метод вызывают из скрипта. Модальное окно встаёт поверх всего, слегка затемняет страницу под собой, переносит фокус внутрь и закрывается по Esc, а щелчки и Tab мимо него до страницы не доходят. Окно с атрибутом open ничего этого не делает: оно просто показано.
Модальные окна есть у браузера и свои — alert и confirm, и поменять их внешний вид страница не может. Их разбирает часть I, статья про диалоги браузера.
Модальное окно отключает всю страницу под собой. Бывает, что отключить нужно только часть: форму, пока идёт оплата, или меню, спрятанное за краем экрана, куда Tab всё равно заведёт человека. Для этого есть атрибут inert — «инертный», безучастный: всё внутри элемента с ним перестаёт отзываться. Ниже <main> с этим атрибутом, в нём кнопка и ссылка, а снаружи ещё одна кнопка. Прогноз: где будет «да», а где «нет»?
<main inert> фокус не приняли, кнопка снаружи — приняла.Одним атрибутом выключено всё содержимое, какие бы теги в нём ни стояли.
Похожее умеет disabled на группе полей <fieldset> — его разбирала статья про кнопки, ссылки и подписи. Но разница есть, и она принципиальная. Поставим ту же пару, кнопку и ссылку, сначала в выключенную группу, потом в инертный блок — и спросим, кто из четырёх элементов примет фокус и сколько окажутся выключенными.
disabled выключает поля формы, а ссылка — не поле. Кнопка и ссылка в инертном блоке не приняли ничего. :disabled насчитал двоих — группу и её кнопку.inert отгораживает всё содержимое, какие бы теги там ни стояли, а disabled — только поля формы. Кнопка в инертном блоке выключенной не считается: inert не меняет состояние элементов, он отгораживает их от человека. Отсюда практическое правило: inert — для области, disabled — для поля.
Выбор между ними короткий. Нужно выключить область — форму на время оплаты, уехавшее за край меню, шаг мастера, который ещё не наступил, — это inert на контейнере: один атрибут вместо обхода всех полей внутри. Нужно выключить конкретное поле или группу полей, оставив человеку понимание, что здесь что-то есть, — это disabled, и причину стоит написать рядом текстом.
Щелчок мыши проходит инертную область насквозь. Проверим topAt(x, y): она отвечает, какой элемент лежит сверху в точке сцены, считая от её левого верхнего угла, или «пусто», если там ничего нет. Стиль дал кнопкам ширину 100 и высоту 30 пикселей и поставил их друг под другом, так что точка 50, 15 приходится на первую кнопку, а 50, 45 — на вторую. Прогноз: какой элемент найдёт прибор в каждой точке?
Программе чтения с экрана инертное содержимое тоже недоступно: браузер убирает его из дерева доступности. Поэтому спрятанное меню не читается вслух как часть страницы — человек не услышит список ссылок, до которых всё равно не добраться.
press щёлкает из кода, и inert его не остановит: атрибут отгораживает область от человека, а не от скрипта. Если press по инертной кнопке отправит форму, прибор не ошибся.
Выглядит инертная область как обычная: браузер её не приглушает. Если человек видит кнопку, которая не нажимается, приглушите область стилем и напишите рядом, почему. У inert статус Baseline Widely available с 11 октября 2025 года — пишите. Но не все браузеры, выпущенные до весны 2023 года, его знают: в них область останется живой.
Ставить и снимать inert и open на ходу — выключить форму на время оплаты и включить после — это уже скрипт. Как он меняет атрибуты и чем атрибут отличается от свойства, покажет часть II, статья про атрибуты и свойства.
Граница «без скрипта» сдвигается: браузеры добавляют поведение, которое раньше писали руками. Но свежее доходит до читателей не сразу. Мерило то же, что для любого тега: Widely — пишите, Newly — знайте, но не делайте единственным путём, Limited — не рассчитывайте.
| Возможность | Что делает | Baseline | Как быть |
|---|---|---|---|
<details> и <summary> | раскрывают ответ щелчком и с клавиатуры | Widely с 15.07.2022 | пишите |
<dialog> и method="dialog" | окно, которое закрывает форма | Widely с 14.09.2024 | пишите |
inert | отгораживает область от фокуса, щелчков и программы чтения | Widely с 11.10.2025 | пишите |
showModal() | модальное окно: Esc, фокус внутри, остальная страница отключена | Widely вместе с <dialog> | нужен скрипт |
name у <details> | открытый блок закрывает остальные с тем же именем | Newly с 03.09.2024 | знайте |
popover | всплывающий элемент, который открывает кнопка | Newly с 27.01.2025 | знайте |
command и commandfor | кнопка открывает и закрывает окно без скрипта | Newly с 12.12.2025 | знайте |
name собирает блоки <details> в группу: у нескольких блоков одно имя, например name="faq", и открытие одного закрывает остальные. Браузер, который атрибут не знает, оставит блоки независимыми — ничего не сломается, просто открытыми могут оказаться несколько ответов сразу.
popover делает элемент всплывающим: он спрятан, пока его не откроет кнопка с атрибутом popovertarget, в котором записан id элемента, и закрывается щелчком мимо или клавишей Esc. В браузере, который атрибут не знает, кнопка ничего не сделает, а сам элемент будет виден на странице всегда.
Команды кнопок (invoker commands) заменяют и вызов showModal(): <button commandfor="confirm" command="show-modal"> открывает окно с id confirm модальным — с Esc и отключённой страницей — без строки скрипта, а command="close" его закрывает. Но в браузере постарше такая кнопка молча не сработает, и окно не откроется вовсе. Поэтому, пока статус Newly, важное окно открывают showModal().
Как плавно проявить содержимое открытого <details> и поповер — тоже без скрипта, одним стилем, — покажет вторая глава, в статье про переходы.
- Самодельный вопрос с ответом скрипт должен учить всему: щелчку, фокусу, клавишам и сообщению для программы чтения.
<details>и<summary>дают это сами, Baseline Widely available с 15 июля 2022 года. - Щелчок, Enter и Пробел на
<summary>ставят и снимают атрибутopen. Закрытый ответ сохраняет свои размеры и стиль, поэтому открытость проверяют у самого<details>: по высоте или свойствуopen. <dialog>безopenспрятан черезdisplay: none. Форма сmethod="dialog"закрывает окно без скрипта и пишетvalueнажатой кнопки вreturnValue. Baseline Widely available с 14 сентября 2024 года.- Esc, фокус внутри окна и отключённая страница — свойства модального окна, а его открывает
showModal(), то есть скрипт. inertотгораживает целую область от фокуса, щелчков и программы чтения, аdisabledвыключает только поля формы. От скриптаinertне защищает. Baseline Widely available с 11 октября 2025 года.nameу<details>,popoverи команды кнопок — Baseline Newly available: знайте о них, но не стройте на них единственный путь к важному.
1/4Два блока <details class="faq">: у summary высота 30 пикселей, у абзаца ответа — 40, первый блок закрыт. Что напечатает measure(".faq p", "height") для абзаца закрытого блока?
2/4В окне <dialog open> форма <form method="dialog"> с кнопкой <button value="later">Позже</button>. Что будет после щелчка по кнопке?
3/4На время оплаты из очереди Tab нужно убрать кнопку «Оплатить» и ссылку «Условия», которые стоят в одном блоке. Что для этого подходит?
4/4Окно <dialog open> не закрывается по Esc. Почему?
Вопросы, которые не раскрываются
На странице «Вопросы и ответы» вопросы набраны заголовками <h3>, ответы — абзацами, и все ответы видны сразу. Переделайте разметку без скрипта: ответ на «Доставка?» закрыт и раскрывается по щелчку, ответ на «Оплата?» открыт с самого начала, оба вопроса принимают фокус.
Классы оставьте: проверка ищет блоки по классу faq, а вопросы — по классу q. Когда всё получится, прибор напечатает .faq → height: 30px, 70px и .q → фокус: да, да.
Каждый блок становится <details class="faq">, заголовок — <summary class="q">. Второму блоку нужен атрибут open, первому — ничего: закрыт он по умолчанию.
Вывод: .faq → height: 30px, 70px и .q → фокус: да, да. Щелчок по «Доставка?» в рамке раскроет первый ответ, а Enter и Пробел на вопросе с фокусом делают то же самое.
Окно, которое не закрывается
Окно спрашивает «Удалить черновик?», но щелчок по «Оставить» отправляет форму, а окно остаётся на месте. Сделайте так, чтобы обе кнопки закрывали окно без скрипта, а по returnValue было видно, какую нажали.
Квадратные скобки в адресе проверяют атрибут: dialog[open] — открытые окна. Нужный вывод — одна строка dialog[open] → 0. Какая кнопка нажата, проверьте, дописав в конец measure("dialog", "returnValue"): получиться должно keep.
Форме не хватает method="dialog". Без него она отправляется как обычная форма — прибор перехватил отправку и напечатал «форма отправлена», — а окно никто не закрывает. Значения у кнопок уже есть, их и запишет returnValue.
Вывод: dialog[open] → 0 и dialog → returnValue: keep. Щелчок по «Удалить» дал бы delete.
Невидимое меню ловит Tab
Закрытое меню сайта спрятано прозрачностью: opacity: 0 делает элемент полностью прозрачным, но оставляет на месте. Глазу меню не видно, а Tab по-прежнему заходит в его ссылки, и человек с клавиатурой теряет фокус в пустоте. Добавьте один атрибут, чтобы ссылки закрытого меню выпали из фокуса, а кнопка «Купить» осталась. Нужный вывод: a, button → фокус: нет, нет, да.
Меню нужен inert: пока оно закрыто, до его ссылок не доходят ни Tab, ни щелчки, ни программа чтения.
Вывод: a, button → фокус: нет, нет, да. Когда меню откроют, скрипт снимет и прозрачность, и inert.