details, summary and dialog: interactive with no JavaScript
An accordion and a modal dialog are the two components every site eventually needs, and the usual route to both is a JavaScript tutorial that produces something keyboard users cannot operate. HTML has them built in. They are accessible by default, they work before your JavaScript loads, and they work if your JavaScript never loads at all.
<details> and <summary>
<details>
<summary>Do you deliver to Karve Nagar?</summary>
<p>Yes, ₹20 for orders under ₹500 and free above it.</p>
</details>
That is a working disclosure widget. Click the summary and it opens; click again and it closes. No CSS, no JavaScript.
What you get for free, and each of these is something hand-rolled accordions usually get wrong:
- Keyboard operation. Tab to it, Enter or Space to toggle.
- The right announcement. A screen reader says "Do you deliver to Karve Nagar, collapsed, button" — and "expanded" after.
- Browser find-in-page opens it.
Ctrl + Ffor text inside a closed<details>finds it and opens the section. Adisplay: noneaccordion hides its content from search entirely. - It prints open in some browsers, and can be forced to.
Rules: <summary> must be the first child, and there is exactly one. Everything
after it is the content. Add open to start expanded:
<details open>
<summary>Opening hours</summary>
<p>7am to 10pm, every day.</p>
</details>
An accordion
<details name="faq">
<summary>Do you deliver?</summary>
<p>Yes, across Kothrud and Karve Nagar.</p>
</details>
<details name="faq">
<summary>What are your hours?</summary>
<p>7am to 10pm.</p>
</details>
Giving several <details> the same name makes them exclusive — opening one
closes the others, which is what an accordion is. This is recent and genuinely
replaced a JavaScript component. Check caniuse.com before relying on it; where
it is unsupported, all the panels simply open independently, which is a perfectly
acceptable fallback.
Styling it
Two things beginners hit immediately.
Removing the triangle:
summary {
list-style: none; /* Firefox, and the standard way */
cursor: pointer;
}
summary::-webkit-details-marker {
display: none; /* older Safari and Chrome */
}
Styling the open state, using a selector from module 6:
details[open] > summary {
font-weight: 600;
}
One real limitation: you cannot animate the open/close transition with CSS alone in
every browser yet, because the content goes from display: none to displayed.
Newer CSS is fixing this; for now, accept the instant toggle. An instant accordion
that works everywhere beats an animated one that traps keyboard users.
And <summary> is already a button as far as assistive technology is concerned.
Do not put a <button> inside it — you get a button inside a button, and the
keyboard behaviour becomes unpredictable.
<dialog>
A modal dialog is where hand-written components fail worst. The hard parts are
focus trapping, returning focus afterwards, Escape to close, and making the rest of
the page inert. <dialog> does all four.
<dialog id="enquiry">
<form method="dialog">
<h2>Send an enquiry</h2>
<label for="phone">Your phone number</label>
<input type="tel" id="phone" name="phone" required>
<button type="submit">Send</button>
<button type="submit" value="cancel">Cancel</button>
</form>
</dialog>
This element does need two lines of JavaScript to open, which is the one exception in this lesson:
<button type="button" onclick="document.getElementById('enquiry').showModal()">
Send an enquiry
</button>
For that price you get:
- Focus moves into the dialog and is trapped there, so Tab cannot wander into the page behind it.
- Escape closes it, which every user expects and most custom modals ignore.
- The rest of the page becomes inert — not clickable, and skipped by screen readers.
- Focus returns to the button that opened it when it closes. Losing this is why custom modals dump a keyboard user back at the top of the page.
- A
::backdroppseudo-element for the dim overlay:
dialog::backdrop {
background: rgb(0 0 0 / 0.5);
}
<form method="dialog"> is the trick worth knowing: a submit button inside it
closes the dialog without submitting anything anywhere, and the button's value
tells you which one was pressed. That is how you get a Cancel button with no
JavaScript.
showModal() versus show()
showModal() gives you everything above. show() opens it as a non-modal panel —
no backdrop, no focus trap, no inertness, and the page behind stays usable. You
almost always want showModal(); show() is for a toast or a picker.
Setting the open attribute directly in HTML also opens it non-modally, and it is
usually a mistake for the same reason.
Do not build a modal out of a div
<!-- the usual tutorial version -->
<div class="modal" style="display:none">…</div>
Focus stays in the page behind it, Escape does nothing, a screen reader reads the
hidden page as if it were still there, and a keyboard user can tab to buttons they
cannot see. Getting all of that right by hand takes about a hundred lines and an
understanding of inert and aria-modal. The element does it.
The other native ones worth knowing
<datalist> — a text field with suggestions, where a value not in the list is
still allowed:
<label for="area">Area</label>
<input type="text" id="area" name="area" list="areas" autocomplete="off">
<datalist id="areas">
<option value="Kothrud">
<option value="Karve Nagar">
<option value="Warje">
</datalist>
That is the difference from <select>: a <select> restricts to the options, a
<datalist> suggests them. Right for a city or area field where you cannot list
everywhere.
<progress> and <meter> — a task's progress, and a measurement in a range:
<progress value="3" max="5">3 of 5</progress>
<meter value="7" min="0" max="10" low="3" high="8">7 out of 10</meter>
Both announce their value to a screen reader. A <div> with a coloured width does
not.
<output> — for a calculated result, which a screen reader announces when it
changes.
The principle
Reach for the native element first. You get keyboard support, screen reader announcements, focus management, find-in-page and printing, all maintained by the browser vendors, at no cost in bytes.
You give up some styling control — that is the honest trade, and it is why design
teams often reject <details> for an accordion that must animate. But the default
should be the native element, with a deliberate reason for leaving it, not the other
way round.
This is also the last time this course asks you to write JavaScript. Everything from here is CSS.
Check your work
What <details> gives you free. Keyboard toggling, correct announcements,
find-in-page opening it, and printing.
Where <summary> must go. First child, exactly one.
How to make an exclusive accordion. Share a name across several <details>.
Why not a <button> inside <summary>. <summary> is already a button.
The <details> limitation. No reliable CSS animation of open and close yet.
Four things <dialog> handles. Focus trapping, Escape, page inertness, and
returning focus.
showModal() versus show(). Modal with backdrop and focus trap, versus a
plain panel.
What <form method="dialog"> does. Closes the dialog on submit without sending
anything; value says which button.
Why a div modal fails. Focus escapes, Escape does nothing, and the hidden page is still read aloud.
<datalist> versus <select>. Suggests versus restricts.
Why <progress> beats a coloured div. It announces its value.
Practice
- Build a three-question FAQ with
<details>and<summary>. Operate it with the keyboard only. - Listen to one with a screen reader. Note the collapsed and expanded announcements.
- Close a
<details>, thenCtrl + Ffor text inside it. Confirm it opens. - Add
name="faq"to all three and confirm only one opens at a time. Check the support on caniuse.com. - Remove the triangle and style
details[open] > summary. - Put a
<button>inside a<summary>and try to use it with the keyboard. - Build a
<dialog>withshowModal(). Tab repeatedly and confirm focus cannot leave it. - Press Escape. Then check where focus went after it closed.
- Style
dialog::backdrop. - Add a Cancel button with
<form method="dialog">and no JavaScript. - Switch to
show()and note everything you lose. - Build the same modal from a
<div>and compare the two on keyboard use alone. - Add a
<datalist>for areas, then type a value that is not in it. - Add a
<progress>and listen to it with a screen reader.
Official documentation
- MDN — The details element — Including the
nameattribute for exclusive accordions. - MDN — The dialog element —
showModal()versusshow(),returnValueand::backdrop. - caniuse — details name attribute — Check support before relying on the exclusive-accordion behaviour.
Next module: CSS — starting with why your unstyled page already has styles.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship