Modal windows: when they are needed and how to make them right
When a window is justified, what it consists of, a bottom sheet on a phone, the keyboard focus, pop-ups on entry and the native dialog element — with live examples.
In short
A modal window interrupts the person, so it is used only when an answer is needed right now: to confirm a deletion, to fill in one short field, to look at a product without leaving the catalogue. Long forms, policies and checkout belong on pages, and a subscription offer belongs after the text, not on top of it at entry. A good window says in the title what it is about, has three ways out — the close button, Esc and Cancel — keeps the keyboard focus inside and returns it to the button that opened it. The page behind is dimmed and does not scroll. On a phone the window becomes a sheet at the bottom of the screen with full-width buttons under the thumb. The native <dialog> element does most of this by itself.
A window or not
Eight common cases. A window is right when the task is short and the answer is needed now.
-
Confirm a deletion
A windowShort, needs an answer right now, and what is being deleted stays visible behind.
-
A one-field “Call me back” form
A windowThe person stays on the page they were reading.
-
A quick product preview
Can beSaves a trip, but the full product page must still exist.
-
Choosing a size or a colour
Can beOn a phone — as a sheet at the bottom.
-
A subscription offer on entry
Not a windowThe person has not read anything yet. A block after the text works better.
-
Checkout
Not a windowA page with steps: it has a Back button and survives a reload.
-
Terms and privacy policy
Not a windowLong text that needs its own address.
-
A “Saved” message
Not a windowA quiet notification that disappears by itself.
What a window consists of
Six parts. Three of them are ways out: the close button, Esc and the backdrop.
- Title — what this window is about, no “Attention!”
- Close — in the top right corner, 44 × 44
- Content — one task, no scrolling inside the window
- Actions — the main one last, its label repeats the point
- Backdrop — dims the page; a click on it closes the window if nothing was typed
- Esc — closes the window from the keyboard
On a phone — a sheet at the bottom
A window in the centre of a phone screen is far from the thumb. A sheet puts the same buttons at the bottom, full width.
In a sheet the main action goes on top of the column, and the grab bar shows that the sheet can be pulled down. If the content does not fit in half the screen, it is no longer a sheet but a page.
The focus stays inside
From the keyboard, Tab walks in a circle inside the window. The page behind cannot be reached, and after closing the focus returns to where it was.
Without this, a keyboard user tabs out of the window into an invisible page, and after closing finds themselves at the top of the document. <dialog> opened with showModal() keeps the focus inside, closes on Esc and makes the page behind inert — no extra library is needed.
Not on entry
A window that covers the article in the first second asks for a subscription before the person knows whether the text is worth it.
The same offer after the text reaches those who have read to the end — the people most likely to subscribe. Google also treats windows that cover the content on a phone as a reason to rank the page lower.
A window in code: 3 examples
The native <dialog>: markup with a form that closes the window, styles with a sheet for phones, and a script for opening and the backdrop.
Markup
A form with method="dialog" closes the window; the value of the pressed button says how.
<button class="button" type="button" data-open="callback">Call me back</button>
<dialog class="dialog" id="callback" aria-labelledby="callback-title">
<form class="dialog__body" method="dialog">
<h2 id="callback-title">We will call you back</h2>
<button class="dialog__close" value="cancel" formnovalidate aria-label="Close">✕</button>
<label for="callback-phone">Phone</label>
<input id="callback-phone" name="phone" type="tel" autocomplete="tel" required>
<div class="dialog__actions">
<button class="button button--ghost" value="cancel" formnovalidate>Cancel</button>
<button class="button" value="send">Call me back</button>
</div>
</form>
</dialog>
Styles
The backdrop, the page that does not scroll, and a sheet on narrow screens.
.dialog {
width: min(100% - 2rem, 34rem);
padding: 0; /* the padding lives inside: a click on the dialog itself is a click on the backdrop */
border: 0;
border-radius: var(--radius-lg);
}
.dialog__body {
display: grid;
gap: var(--space-3);
padding: var(--space-5);
}
.dialog::backdrop {
background: rgb(23 24 23 / 0.6);
}
/* The page behind does not scroll */
html:has(.dialog[open]) {
overflow: hidden;
}
/* On a phone: a sheet at the bottom with full-width buttons, the main one on top */
@media (max-width: 47.99rem) {
.dialog {
width: 100%;
max-width: none;
margin: auto 0 0;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.dialog__actions {
display: flex;
flex-direction: column-reverse;
gap: var(--space-2);
}
}
Opening and the backdrop
showModal() does the hard part; the script adds closing by the backdrop and the return of the focus.
// The button opens its window; after closing, the focus returns to the button
for (const opener of document.querySelectorAll('[data-open]')) {
const dialog = document.getElementById(opener.dataset.open);
opener.addEventListener('click', () => {
dialog.showModal();
dialog.addEventListener('close', () => opener.focus(), { once: true });
});
}
// A click on the backdrop closes the window — unless something has been typed
for (const dialog of document.querySelectorAll('dialog')) {
dialog.addEventListener('click', (event) => {
const typed = [...dialog.querySelectorAll('input, textarea')].some((field) => field.value);
if (event.target === dialog && !typed) dialog.close('cancel');
});
}
7 rules for modal windows
-
01
Only when the answer is needed now
Everything else — a page, a block or a notification.
-
02
A title that says what it is about
“Remove from the cart?”, not “Attention!”.
-
03
Three ways out
The close button, Esc and Cancel; the backdrop — if nothing has been typed.
-
04
The focus inside and back
Tab does not leave the window, and after closing returns to the button.
-
05
A sheet on a phone
Buttons at the bottom, full width, under the thumb.
-
06
One window at a time
The next step replaces the content instead of opening a window on top.
-
07
Not on entry
A window opens in response to an action of the person.
Common mistakes with modal windows
-
A pop-up in the first second
People close it without reading and remember the annoyance.
-
A tiny or late close button
The person looks for a way out instead of reading.
-
The backdrop erases what was typed
One click past the window — and the form has to be filled in again.
-
The page scrolls behind
After closing, the person is in a different place on the page.
-
A window on top of a window
It is not clear which one closes and where the person returns.
-
A long form in a window
A scroll inside a scroll, and on a phone the buttons are out of reach.
Questions about modal windows
A modal window or a separate page?
A page if the content is long, needs its own address or a Back button. A window for a short task without leaving the page.
Should a click on the backdrop close the window?
Yes, if nothing has been typed. If the person has started filling in a form, a click past the window should not erase it.
What does the dialog element give?
Opened with showModal(), it dims the page, keeps the focus inside, closes on Esc and is understood by screen readers. All modern browsers support it.
How should a modal window look on a phone?
As a sheet at the bottom with full-width buttons. If the content is longer than half the screen, it is better as a page.
Can a window open by itself?
Only for important warnings, like the end of a session. Everything else opens in response to an action.
How wide should a window be?
As wide as the content needs without scrolling inside; for dialogs with text — up to about 560 px.
Online form
Windows that
do not annoy
I design and build interfaces where dialogs appear only when needed and close the way people expect. Tell me about the project — I answer within one working day.