Open the full-page example → · Download complete recipe (ZIP)
Try the working example
The preview runs only our supplied example code. Entries stay in the page; the practice form cannot send them. For keyboard testing, use the full-page example.
Build it step by step
- Use a dialog element with a heading referenced by aria-labelledby. Keep the opener hidden until the feature is ready.
- Call showModal(), rather than toggling the open attribute, to make the rest of the document inert while it is open.
- Place initial focus on the close button for this short informational dialog. Native Escape behaviour closes it too.
- Return focus to the opener on close. Test Tab, Shift+Tab, Escape and reopening the dialog.
Why it works
The native modal supplies behaviour that a styled div does not: a top layer, an inert background and keyboard dismissal. Initial focus still needs a decision based on the content. Our short note puts it on its only action.
A mistake worth catching
Setting open directly displays a non-modal dialog. Also, hiding a dialog with CSS does not run its close lifecycle. Use showModal() and close() for this example.
Read the complete source
The ZIP also includes the local font and its licence, a README and any illustrations. The source below is the same code used in the preview.
HTML source
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Mario V.W.B.R. Obst"><meta name="robots" content="noindex, follow"><meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'none'; form-action 'none'; object-src 'none'; base-uri 'none'">
<title>A dialog with a way back</title><link rel="stylesheet" href="style.css">
<script src="script.js" defer></script></head>
<body><main><h1>Take a closer look.</h1><p>A dialog interrupts the current task. Use one only when a separate decision or detail really needs it.</p><button id="open" type="button" hidden>Read the observation note</button><p id="fallback">Observation note: pause, look again, and write down one detail you missed. An enhanced dialog is available when supported.</p><dialog id="note" aria-labelledby="note-title"><h2 id="note-title">One more look</h2><p>Pause, look again, and write down one detail you missed.</p><button id="close" type="button" autofocus>Close note</button></dialog><p><a href="#after">Continue reading</a></p><h2 id="after">Back to the page</h2><p>The open button should receive focus again when the dialog closes.</p></main></body></html>CSS source
@font-face{font-family:FYI;src:url("inter.ttf") format("truetype");font-weight:100 900;font-display:swap}
*{box-sizing:border-box}html{color-scheme:dark}body{margin:0;background:#20212a;color:#efedf6;font-family:FYI,sans-serif;line-height:1.6}main{max-width:960px;margin:auto;padding:24px}h1,h2{line-height:1.15}a{color:#ff9b45}button,input,select,textarea{font:inherit;color:inherit;background:#282934;border:1px solid #777582;border-radius:6px;padding:10px}button{cursor:pointer}button:disabled{cursor:default;opacity:.5}:focus-visible{outline:3px solid #ff9b45;outline-offset:4px}label{display:block;margin-top:16px}button{margin:12px 8px 12px 0}p{max-width:70ch}.hint{color:#c7c3d2} [hidden]{display:none!important}.panel{padding:20px;border:1px solid #555361;border-radius:12px;background:#282934}img{max-width:100%;height:auto}code{overflow-wrap:anywhere}input,select,textarea{max-width:100%}a,button{touch-action:manipulation}
dialog{max-width:min(90vw,480px);background:#282934;color:#efedf6;border:1px solid #ff7700;border-radius:14px;padding:28px}dialog::backdrop{background:rgb(0 0 0 / .75)}JavaScript source
const dialog = document.querySelector('#note');
const opener = document.querySelector('#open');
if (typeof dialog.showModal === 'function') {
opener.addEventListener('click', () => dialog.showModal());
document.querySelector('#close').addEventListener('click', () => dialog.close());
dialog.addEventListener('close', () => opener.focus());
document.querySelector('#fallback').hidden = true;
opener.hidden = false;
}
Take it one step further
Add a second paragraph and test at 200% zoom. If the content grows long, reconsider where initial focus should land instead of automatically focusing an action near the bottom.
Check your work
Try a narrow window, 200% zoom and keyboard-only operation. Disable JavaScript to inspect the fallback. For motion, test your reduced-motion preference. Do not use real personal details in learning forms.
Keep exploring
Read the companion guide · Practise finding small mistakes · All code recipes
Technical reference and authorship
Read the underlying platform documentation (new tab). The explanation, composition and example were written for this site; this link is a factual reference, not a source of a copied template. Inter has its own font licence in the download.
Use and adapt the original example for your own learning. For other reuse, see Legal information. About AI assistance.