By Mario V.W.B.R. Obst · Original learning example · September 2026

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

  1. Use a dialog element with a heading referenced by aria-labelledby. Keep the opener hidden until the feature is ready.
  2. Call showModal(), rather than toggling the open attribute, to make the rest of the document inert while it is open.
  3. Place initial focus on the close button for this short informational dialog. Native Escape behaviour closes it too.
  4. 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
HTML · EXAMPLE
<!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
CSS · EXAMPLE
@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
JavaScript · EXAMPLE
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.