Learning objectives
- Build disclosure widgets and exclusive accordions with
details,summaryandname, and style them without breaking them - Choose between a non-modal and a modal
dialog, and say exactly whatshowModal()does to focus, the rest of the page and the Escape key - Build popovers with the
popoverattribute andpopovertarget, and tellauto,manualandhintapart - Wire buttons to dialogs and popovers with
commandforandcommand, with a fallback for browsers that lack them - Choose correctly between
meterandprogress, and give each a label and a text equivalent - Explain the top layer, focus return and inertness, and why a native element beats a
divwith a role
Interaction before any script runs
A link already goes somewhere when clicked, and a form already sends a request when submitted, with no script on the page. Those two have their own sections: Linking draws the line between a link, which goes somewhere, and a button, which does something, and Forms covers every control that collects data. This lesson is about the rest of HTML's interactive vocabulary: elements that show, hide, overlay and measure.
The course slides end their tour of HTML elements with a question. Browsers had started shipping whole widgets, such as <dialog>. Should they keep adding elements, for tabs, tree controls and "toast" messages, until HTML is a complete UI toolkit? The slides give three answers. Yes, because nothing beats native. No, because the standards process is slow, so build the missing pieces yourself as web components. And the likely one: components first, native second, with each new element absorbing a pattern that components proved.
That third answer is what happened. Accordions, modal dialogs, dropdown menus and tooltips were each a library a few years ago. The platform absorbed each into an element or an attribute, and the result works like a component library that ships with every browser, already wired to the keyboard, the accessibility tree and features such as find in page. What remains JavaScript's job is what depends on your application: what a custom command does, how far a real upload has got, what to do with a dialog's answer.
Several of these features are new, so each section says where it stands in October 2026, and each demo prints what your browser supports. All of them degrade: a browser ignores an attribute it does not know, as lesson 03 showed, so the page still works, more plainly. That is progressive enhancement, and it is how to use the newest of them.
details and summary
The disclosure widget
The specification defines <details> as "a disclosure widget from which the user can obtain additional information or controls." Its first child is a <summary>, which stays visible as the toggle; everything after the summary is shown or hidden.
The browser does the rest: the summary takes focus with Tab, Enter and Space toggle it, a click toggles it, and a triangle marker shows the state. Leave out the summary and the browser supplies one that reads "Details" (localized), which is rarely the label you want. The content model is "one summary element followed by flow content," so a details can hold paragraphs, lists, tables, even forms. details is in the interactive content category described in lesson 04, which is why it may not go inside a link or a button.
The open attribute and the toggle event
State lives in one boolean attribute. <details open> starts open; the browser adds and removes open as the user toggles, so the attribute in the DOM is always the truth. A script can read or set the open property, and CSS can select details[open]. After each change the element fires a toggle event, whose oldState and newState properties say "open" or "closed". The event is fired asynchronously, after the change, so it reports what happened; it cannot cancel it.
Exclusive accordions with name
Give several details elements the same name and they form an exclusive group: opening one closes whichever other member was open. All of them may still be closed at once. The mechanism is borrowed from radio buttons, where a shared name has grouped inputs since the beginning of HTML.
This one attribute replaced the accordion script that used to ship with nearly every FAQ page. It is Baseline: Chrome 120 and Safari 17.2 shipped it in December 2023, and Firefox 130 completed the set in September 2024. In an older browser the attribute is ignored and the group behaves as independent disclosures, a usable fallback. Two rules come with it. The markup may give open to at most one member of a group. And the specification asks authors to think before using it at all: an exclusive accordion saves space, but it "can also frustrate users who have to open many items to find what they want or users who want to look at the contents of multiple items at the same time." For a long FAQ that people scan, independent disclosures are often kinder.
Find in page opens closed details
Content inside a closed details is not rendered, yet it is not lost to the browser's search. When find in page, or a text fragment link, matches text inside a closed details, a supporting browser opens the element to show the match and fires its toggle event. Chrome has done this since version 97 (2022). Firefox 139 and Safari 26.2 added it in 2025, though MDN's compatibility data marks both partial because they do not scroll correctly to the match (Firefox 148 fixes that). Elsewhere, the search simply finds nothing in the closed details. The same machinery powers hidden="until-found", covered in lesson 03.
Styling the marker and the content
The summary is displayed as a list item, so its triangle is a ::marker, and list-style: none on the summary removes it in current browsers, including Safari since version 18.4 (2025). Safari still supports only color and font-size on ::marker, and older versions draw their own marker, which needs the prefixed ::-webkit-details-marker pseudo-element to hide. A robust custom marker hides both and draws its own:
The content used to be impossible to animate, because it went from not rendered to rendered with nothing in between. The ::details-content pseudo-element, which wraps everything after the summary, fixes that. It reached Chrome 131, Safari 18.4 and Firefox 143, which made it Baseline in September 2025. Combined with interpolate-size, which lets a size transition run to auto and is so far in Chromium only, the content can slide open. Wrap it in @supports so other browsers simply show the content at once:
Accessibility
The summary behaves as a button that reports whether its content is expanded. Chrome's accessibility tree exposes it as a disclosure control with an expanded state and the summary's text as its name, and the details as a group; screen readers differ in wording, but most announce something like "button, collapsed." Two habits keep that working. Do not remove the summary's focus outline without replacing it. And do not put links, buttons or form controls inside the summary. The content model allows phrasing content, links included, so the validator will not object, but a control inside a control is two click targets in one, and a screen reader may flatten the summary into one button name. A heading inside the summary is fine, and useful when each disclosure is a section of the page.
dialog
The specification defines <dialog> as "a transitory part of an application, in the form of a small window." It has been Baseline since March 2022, when Firefox 98 and Safari 15.4 joined Chrome. A closed dialog is not rendered. It opens in one of two ways, and the difference between them is the most important thing to learn about the element.
show() and showModal()
dialog.show() opens it non-modally. The dialog appears in the page's normal stacking order and the rest of the page stays fully usable: an inspector panel, a "saved" notice, a chat window. Escape does nothing to it by default.
dialog.showModal() opens it modally, and the browser then does five things for you, each checked in Chrome for this lesson:
- The rest of the document becomes inert. Clicks behind land on the backdrop, nothing back there can take focus, and the background leaves the accessibility tree, so a screen reader cannot wander out of the dialog.
- The dialog moves to the top layer, above everything else in the page, whatever their
z-index. - A
::backdropis drawn between the page and the dialog, for you to style. - Focus moves into the dialog, to the element with
autofocusif there is one, otherwise to the first focusable element, otherwise to the dialog itself. Tab then cycles through the dialog's controls and the browser's own toolbar, never the page behind. - Escape closes it. The browser fires a cancelable
cancelevent, then closes the dialog and firesclose.
On closing, modal or not, focus returns to the element that had it when the dialog opened, usually the button that opened it. The specification calls this the dialog's "previously focused element." Hand-built modals often get this wrong, and keyboard users notice: without it, closing a dialog drops them at the top of the page. One rule is easy to break: the specification forbids tabindex on a dialog, because the browser decides where focus goes.
z-index, overflow: hidden or transform on an ancestor can trap a modal dialog or a popover beneath other content.The top layer is why a native modal never appears behind a sticky header or gets clipped by overflow: hidden, bugs that hand-built modal libraries work around by moving the modal's markup to the end of body. Popovers and full-screen elements share the same plane.
Closing with a form: method="dialog"
A form inside a dialog can use method="dialog". Such a form sends nothing: submitting it closes the dialog, and the submitting button's value becomes the dialog's returnValue. Forms Basics places this in the submission lifecycle; here is the dialog's side.
Put autofocus on the safe choice, so that a stray Enter cancels rather than deletes. Escape closes the dialog without touching returnValue, which keeps whatever it held before, so reset it if the same dialog opens more than once. A script can also close a dialog with dialog.close('value'), and dialog.requestClose() (Chrome 134, Firefox 139, Safari 18.4) closes it the way Escape does, firing a cancel event that a listener can cancel, for example to ask about unsaved changes.
This is also the end of alert(), confirm() and prompt() in new code: they block the page's script, cannot be styled, and are increasingly restricted by browsers.
closedby: how the user may dismiss it
By default a modal dialog closes on Escape but not on a click outside it, and a non-modal dialog closes on neither. The closedby attribute changes that. closedby="any" adds light dismiss, a click outside the dialog; closedby="closerequest" allows only Escape and other close requests, such as Android's back gesture; closedby="none" allows neither, for a dialog the user must answer. It works on non-modal dialogs too. Chrome 134 and Firefox 141 support it; Safari has it only in Technology Preview as of October 2026. So never let closedby="any" be the only way out: keep a visible close button.
Opening a dialog without script
For most of its life, dialog had one gap: closing it needed no script, but opening it did, because showModal() is a method. Invoker commands, covered below, close the gap: a button with commandfor="confirm-delete" and command="show-modal" opens that dialog modally, and one with command="close" closes it.
Popovers
A popover is content that appears above the page on demand and goes away again: a menu, a flyout, a note, a toast. Unlike dialog, popover is not an element but a global attribute, so any element can be one. It is Baseline since January 2025: Chrome shipped it in version 114 (2023), Safari in 17 (2023) and Firefox in 125 (2024), and the last iOS fix landed in Safari 18.3.
popover and popovertarget
The popover attribute hides the element until it is shown. A button's popovertarget names the popover by id and toggles it; popovertargetaction="show" or "hide" makes the button only open or only close it. An open popover goes into the top layer, above everything, and matches the :popover-open pseudo-class for styling. Script has showPopover(), hidePopover() and togglePopover(), and the popover fires toggle as details does. popovertarget works only on buttons, which is right: opening a popover does something; it does not go anywhere.
auto, manual and hint
The attribute takes three values, and the difference is how a popover closes and what it closes.
popoverorpopover="auto"light-dismisses: a click outside it or Escape closes it. Opening one auto popover closes any other open auto popover, unless the new one is nested inside it, as a submenu is inside a menu. Use it for menus, pickers and flyouts.popover="manual"has no light dismiss and closes no others. It stays until a button or a script closes it, so give it a close button. Use it for toasts and anything that should survive clicks elsewhere.popover="hint"is meant for tooltips. It light-dismisses likeauto, but opening it leaves open auto popovers alone, so a tooltip can appear over an open menu; opening an auto popover closes it, and a second hint replaces the first.
hint is the one value to treat with care. Chrome 133 and Firefox 149 shipped an earlier version of its rules, and the current behavior arrived only in Chrome 151 and Firefox 153; Safari has it in Technology Preview. A browser that does not recognize the value treats the popover as manual, the default for an invalid value, so an unsupported hint still opens and closes with its button but does not light-dismiss.
Light dismiss and focus
"Light dismiss" means closing without a deliberate close action: a click or tap outside, or Escape. Users expect it of a menu, and it is tedious to build by hand. When you test, remember that clicking a button outside an open auto popover is itself a click outside, so the popover closes before the button acts.
Unlike a dialog, a popover does not take focus when it opens. But the browser puts it next in the Tab order after its button, wherever it sits in the markup, so one Tab moves into the menu, and if focus is inside when it closes, focus returns to the button. Nothing behind a popover becomes inert: it is not modal.
A popover is not a dialog
The popover attribute adds behavior, not meaning. It gives the element no role, so choose the element for what the content is: a nav or a list for a menu of links, an aside for a note, a dialog if it really is a small window that asks something. A dialog element can itself carry popover, which gives you a dialog with light dismiss and no modality.
The button does get something. In Chrome's accessibility tree, a button with popovertarget reports an expanded state, as aria-expanded would, and when the popover is not the next element after the button, Chrome also links the two with a details relationship, as aria-details would. Neither needs ARIA from you. Screen readers differ in how they announce both.
Placing a popover: anchor positioning
The top layer solves stacking, but by default a popover appears in the middle of the viewport, which suits a toast and not a menu. CSS anchor positioning tethers a positioned element to another element, and a button with popovertarget automatically becomes its popover's anchor, so no names are needed:
The core properties are in Chrome 125, Safari 26 and Firefox 147, and the automatic anchor from popovertarget in Chrome 133, Safari 26 and Firefox 147. The engines still differ in details, so the web-features project does not yet count anchor positioning as Baseline. Treat it as an enhancement: without it, the popover appears centered and still works. The CSS layout lesson covers positioning in general.
Invoker commands
popovertarget proved the idea: a button names its target in markup and the browser carries out the action. Invoker commands generalize it. A button's commandfor attribute names the target element by id, and its command attribute names the action:
command | Target | Effect |
|---|---|---|
show-modal | dialog | As showModal() |
close | dialog | Closes it; the button's value becomes returnValue |
request-close | dialog | As requestClose() |
show-popover, hide-popover, toggle-popover | any popover | The same as popovertargetaction's three values |
-- plus any name | any element | Only the command event, for a script |
Before the browser runs a command, it fires a cancelable command event at the target. The event's command property holds the command and its source property the button. For a built-in command a listener can cancel the default; for a custom command, one whose name starts with two dashes like a CSS custom property, the event is all that happens: the button says what it wants in markup, and the target decides what that means.
command event at the target, then runs the built-in action. An older browser ignores both attributes, which is what makes a small fallback script safe to add.Current support, and the fallback
Invoker commands arrived in Chrome 135 (April 2025), Firefox 144 (October 2025) and Safari 26.2 (December 2025), which made them Baseline in December 2025. The request-close command followed in Chrome 139. "Baseline newly available" means every current browser has it, not every browser in use: anyone on a browser older than those versions has none of it. A related proposal, interestfor, which shows a target on hover or focus rather than on a click, is in Chrome 142 only and still experimental.
A button whose invoker attributes are ignored does nothing at all, which is worse than no button. So, for now, pair invokers with a few lines that do the same job where they are missing. Detect the feature, not the browser:
In a current browser the if is false and the markup does all the work. As the older browsers leave circulation, the fallback can be deleted without touching the HTML. The dialog lab runs this pattern, and its readout says which path opened each dialog.
meter and progress
Two elements draw a bar, and they are easy to confuse. The specification separates them by what the number means.
meter: a gauge
<meter> "represents a scalar measurement within a known range, or a fractional value": disk usage, a battery level, a password's strength, the share of votes a candidate has. It is also known as a gauge. The range must really be known; the specification says it would be wrong to use meter for a weight or a height "unless there is a known maximum value." Its attributes are value, min and max (0 and 1 by default), plus three that describe what is good:
lowandhighcut the range into a low, a middle and a high region;optimumsays which region is the good one.
The browser colors the bar by how far the value's region is from the optimum's: optimum (green in Chrome), suboptimal (yellow), or, at the far end, "even less good" (red). The values must keep this order: min ≤ low ≤ high ≤ max, with value and optimum inside the range.
optimum low, humidity in the middle, a battery high; the browser colors the bar by the distance between the value's region and the optimum's.progress: a task
<progress> "represents the completion progress of a task": an upload, an install, step three of five. It has only value and max (1 by default). The specification is explicit that the two are not interchangeable: meter "should not be used to indicate progress (as in a progress bar)," and a disk that is 70% full is a measurement, not a task that is 70% done.
Leave out value and the bar is indeterminate: work is happening, but nobody knows how much remains, such as while waiting for a server. The browser animates it, its position property returns -1, and CSS selects it with :indeterminate. Setting value later makes it determinate.
Neither is a control; both need a label
Neither element is interactive. Neither takes focus, and the user cannot change either one; only your script can, by setting value. They are output, and like output they need two things to be accessible. The first is a name: both are labelable, so a <label for> (or aria-labelledby) names them, and Chrome exposes them with the roles meter and progressbar. The second is a text equivalent: the content between the tags is not rendered by browsers that draw the bar, but the specification encourages it for user agents that cannot. A colored bar alone tells someone who cannot tell green from red very little, so when the figure matters, say "64%" or "Low" in visible words as well.
Two quieter elements: output and template
output
The course slides describe <output> as "a region that will be used as output from some calculation or form control." Its for attribute lists the ids of the controls that produced the result, and browsers expose it with the status role, a polite live region, so many screen readers announce changes to its text. This course uses it for every form message, the hint under a field and the error that replaces it; Organizing forms builds that pattern in full.
template
<template> is the reverse of the rest of this lesson: content the browser deliberately does nothing with. In the slides' words, it defines "inert content that is used for cloning and insertion by script," a scratch space in the document for partial views. Nothing in it renders, loads or runs until a script clones it. Lesson 03 covers how the parser treats it, and templates return with web components in Components.
The common thread: native beats div
Look back across the lesson and the same few mechanisms keep appearing. Dialogs and popovers share the top layer, so stacking is solved once, by the browser. Modal dialogs use inertness, the same mechanism as the inert attribute from lesson 03, to take the rest of the page out of reach of the mouse, the keyboard and assistive technology at once. Dialogs, popovers and details all manage focus: where it goes on opening, where it returns on closing, and what order Tab follows. And every widget reports its state to the accessibility tree, open or closed, expanded or collapsed, modal or not, without a line of ARIA from you.
A hand-built widget has to do all of that itself, and usually does it partly. A <div class="modal"> with a click handler does not make the page behind it inert, so Tab walks out into the page; it does not return focus when it closes; it gets trapped under a sticky header by a z-index war; and a screen reader does not announce it as a dialog unless someone adds the right roles and keeps them in sync with its state. This is the reason for the first rule of ARIA, in the W3C's Using ARIA: "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so." ARIA changes what an element announces, not how it behaves; the native element brings both.
JavaScript is still needed, but for the right jobs: deciding what a returnValue means, handling a custom command, reporting real progress, filling a template with data, and, for a little longer, supplying invokers where they are missing. The division of labor is the platform's behavior in the markup and the application's logic in the script. The Accessibility page follows the same thread through the rest of the course.
Try it: replace a script with markup
Four exercises, each about fifteen minutes. Add script only where an exercise asks for it.
- An FAQ. Write five questions and answers as
detailselements. Decide, and write one sentence on why, whether they should share aname. Give the summaries a custom marker that works in Safari too, and check that Tab, Enter and Space still work and that the focus outline is visible. - A confirmation. Build a "Leave without saving?" modal
dialogwith Stay and Leave buttons in amethod="dialog"form, opened by a button withcommandforandcommand="show-modal". Add the feature-detected fallback, then test it by temporarily changing the condition totrue. LogreturnValueonclose, and check where focus lands after each way of closing. - A menu and a toast. Put a navigation menu in an auto popover and a "Settings saved" message in a manual popover with its own close button. Open both, and explain in one sentence why only one closes on Escape. Where anchor positioning is supported, place the menu under its button.
- The right bar. For each of these, choose
meter,progressor neither, and write the markup with a label and a text equivalent: storage used out of a 15 GB quota; a file upload; a person's height; how many of eight lessons a student has finished; a password's strength from weak to strong. Then open the accessibility pane in DevTools and confirm the role and name of each.
Summary
| Concept | Key points |
|---|---|
| Interaction without script | Accordions, modals, menus and tooltips are now elements and attributes. Unknown attributes are ignored, so new features degrade to plainer behavior. |
details / summary | open is the state; toggle fires after each change. Find in page opens a closed details (Chrome; Firefox and Safari partly). Hide the marker with list-style: none and ::-webkit-details-marker. No controls inside the summary. |
name on details | A shared name makes an exclusive accordion; Baseline since September 2024. Ask whether exclusivity helps users. |
dialog | show() is non-modal: page usable, Escape does nothing. showModal(): page inert, top layer, ::backdrop, focus moved in, Escape closes. Focus returns on close. No tabindex on the dialog. |
method="dialog" | Submitting closes the dialog and sets returnValue to the button's value; nothing is sent. Escape leaves returnValue unchanged. requestClose() fires a cancelable cancel. |
closedby | any, closerequest or none. Chrome 134 and Firefox 141; Safari only in Technology Preview. Always keep a close button. |
popover | Baseline since January 2025. auto light-dismisses and closes other autos; manual stays until closed; hint leaves autos open (Chrome 151, Firefox 153). Top layer, not modal, no role of its own. |
| Anchor positioning | position-area places a popover next to its invoking button, its implicit anchor. In all three engines, not yet Baseline; centered fallback. |
| Invoker commands | commandfor names the target, command the action, built in or --custom. A cancelable command event fires first. Baseline since December 2025; add a feature-detected fallback. |
meter vs progress | meter is a gauge within a known range, judged by low, high and optimum. progress is task completion; no value means indeterminate. Neither is interactive; both need a label and a text equivalent. |
output and template | output holds results and messages, exposed as a polite status region. template holds inert markup for scripts to clone. |
| Gone or never shipped | contextmenu and menu type="context" are removed; menu is a list. selectmenu never shipped; the customizable select replaced it. |
| Native beats div | Top layer, inertness, focus and state come free. First rule of ARIA: use the native element. |
Next steps
This lesson ends the Semantics group. Lesson 09, The head and the machine-readable layer, turns from the body to the part of the document people never see, written for browsers, search engines, social networks and AI agents. The behaviors this lesson left to script are taught in Events, which covers listeners such as the close, toggle and command handlers used here, and in Components, which builds the widgets HTML does not have yet.