Overzicht
Gebruik het systeemeigen element <dialog> voor modale en niet-modale dialoogvensters. Het biedt de toegankelijkheidssemantiek, focusverwerking, weergave van de bovenste laag en Esc-verwerking die aangepaste overlays handmatig moeten herbouwen.
Het overlaytype kiezen
Kies de eerste regel die overeenkomt. Als u het niet zeker weet, gebruikt u regel 1.
Regel 1: De overlay onderbreekt de gebruiker en blokkeert de pagina (standaard)
Gebruik deze voor bevestigingen, formulieren, inloggen, destructieve acties en elk modaal, dialoog-, lightbox- of pop-upverzoek dat niet anders aangeeft.
Gebruik <dialog> geopend als een modaal, declaratief met command="show-modal" (zie Declarative Dialog and Popover Control) of vanuit een script met showModal().
<button type="button" command="show-modal" commandfor="confirm">Delete</button>
<!-- MANDATORY: no role="dialog" or aria-modal; the native modal already provides them -->
<dialog id="confirm" aria-labelledby="confirm-title">
<h2 id="confirm-title">Delete this item?</h2>
<form method="dialog">
<button value="cancel" autofocus>Cancel</button>
<button value="delete">Delete</button>
</form>
</dialog>
De browser biedt: inerte pagina, focusbeperking, bovenste laag, ::backdrop, Esc en sluiten met teruggebaar, en focus terug naar de aanroeper.
- Voeg GEEN
role="dialog",aria-modal, focusval,inert-schakelaar ofEsc-handler toe. - Open het NIET met
show()of het kenmerkopen. - WEL: Stop het scrollen van de pagina achter de overlay met de css hieronder. Ga naar Voorkomen dat de pagina onder een modaal dialoogvenster scrollt voor meer informatie.
- Vergrendel scrollen NIET met
overflow: hiddenofposition: fixedophtmlofbody.
/* MANDATORY (both rules): without them, scrolling over the backdrop or inside the dialog scrolls the page behind it */
dialog:modal {
overscroll-behavior: contain; /* stops scroll gestures in the dialog chaining to the page */
}
dialog::backdrop {
overflow: hidden; /* ::backdrop is not a scroll container by default, so it would ignore overscroll-behavior */
overscroll-behavior: contain;
}
Niet voor menu's of tooltips: zie regel 3.
Regel 2: De gebruiker blijft op de pagina werken terwijl het deelvenster is geopend
Gebruik dit voor zoekbalken, inspecteurs, paletten en opstelvensters die semantisch nog steeds dialoogvensters zijn.
Gebruik <dialog popover> voor een deelvenster op de bovenste laag met popover-sluiting (auto sluit met een lichte tik, manual niet). Gebruik dialog.show() alleen als het deelvenster niet in de bovenste laag mag staan.
<button type="button" popovertarget="find">Find</button>
<dialog id="find" popover="manual" aria-labelledby="find-title">
<h2 id="find-title">Find</h2>
<input type="search" aria-label="Search">
<button type="button" popovertarget="find" popovertargetaction="hide">Close</button>
</dialog>
De pagina blijft interactief en wordt niet inactief gemaakt.
- WEL een zichtbare knop Sluiten opnemen.
- Gebruik GEEN
showModal(), omdat dit de pagina blokkeert.
Niet voor blokkeringsbevestigingen: zie regel 1.
Regel 3: De overlay is geen dialoogvenster
Gebruik dit voor menu's, dropdownmenu's, tooltips, hovercards en toasts.
Gebruik een semantisch element met het kenmerk popover, niet <dialog>.
<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
<li><a href="/rename">Rename</a></li>
</ul>
Kiezen hoe de melding kan worden gesloten
De standaard hangt af van hoe het dialoogvenster wordt geopend. Als closedby ontbreekt of ongeldig is, gedraagt showModal() zich als closerequest en gedraagt elke andere manier van openen zich als none.
- Alleen Esc- en teruggebaar (modale standaard): stel niets in of
closedby="closerequest". - Ook buiten klikken om te sluiten: gebruik
closedby="any". - Alleen de ontwikkelaar sluit het (bijvoorbeeld een formulier met niet-opgeslagen wijzigingen): gebruik
closedby="none"en voeg een bevestigingspad toe. Zie Afsluiting hieronder.
Richtlijnen
Toegankelijke naam en eerste aandacht
- WEL: Geef elk dialoogvenster een naam met
aria-labelledbydie naar de kop verwijst ofaria-labelals er geen zichtbare titel is. - WEL: Zet
autofocusop het primaire bedieningselement of het eerste veld. - WEL: Zet
autofocusop de veilige actie (zoals Annuleren of Behouden) in een bevestiging van een destructieve actie. - Schrijf GEEN code om de focus terug te zetten bij sluiten. De browser stuurt deze terug naar de aanroeper.
Openen
- Open WEL declaratief met
command="show-modal"encommandforop een<button>. Ga naar Declarative Dialog and Popover Control (Declaratieve dialoogvenster- en pop-over-beheer). - Gebruik WEL
dialog.showModal()als je opent vanuit een script.
Afsluiting
- Sluit WEL af met
<form method="dialog">-knoppen ofcommand="close". - Kies
closedbybewust:any(licht sluiten enEsc),closerequest(alleenEsc, de standaardinstelling voor de modus) ofnone(alleen sluiten door ontwikkelaar). - Gebruik WEL
command="request-close"ofrequestClose()voor de knop Annuleren als de logica voor niet-opgeslagen wijzigingen moet worden gerespecteerd. Ze activeren eerstcancelen negerenclosedby. - WEL DOEN: Behandel
closedbyals progressive enhancement.Escen het mobiele gebaar voor terug sluiten al modale vensters zonder dit.
Hooking into close
- Lees
dialog.returnValueWEL in eenclose-listener om te weten welke knop is gebruikt en reset deze metdialog.returnValue = ''.Escen andere verzoeken om te sluiten stellen geen waarde in. Browsers verschillen in of ze de vorige waarde wissen, dus zonder de reset kan een verouderde waarde lekken.''behandelen als 'gesloten zonder keuze'. - WEL
cancelgebruiken omEscenrequest-closete onderscheppen, maar vertrouw niet oppreventDefault()voor herhaaldelijk drukken opEsczonder gebruikersactivatie. - WEL: Gebruik
closedby="none"met een expliciete bevestigingsstap voor onvolledige formulieren. - WEL de gebruiker vertellen waarom het dialoogvenster open is gebleven als
cancelwordt voorkomen: toon een zichtbaar bericht en verwijs ernaar metaria-describedbyvanuit het bedieningselement dat de focus krijgt (zoals Verwijderen), zodat een schermlezer het bericht voorleest als de focus wordt verplaatst. Zonder deze functie wordt alleen de naam van de nieuwe knop aangekondigd als de focus naar een nieuwe knop wordt verplaatst. - Laat de bevestiging NIET de bedieningselementen onder de cursor verplaatsen. Reserveer de ruimte van het bericht (verberg het met
visibility, niet metdisplayofhidden) en voeg de destructieve knop toe op een plek waar bestaande knoppen niet worden verplaatst (bijvoorbeeld de eerste in een rechts uitgelijnde rij), zodat een tweede klik op dezelfde plek de wijzigingen van de gebruiker niet ongedaan kan maken.
Niet-modale dialoogvensters
- Gebruik WEL
<dialog popover>(autoofmanual) voor een deelvenster op de bovenste laag endialog.show()als je de bovenste laag niet wilt. - WEL: Voeg een zichtbare sluitknop toe aan elk niet-modaal dialoogvenster.
Achtergrond en styling
- DO style
::backdropand usedialog:modalto target modal-only styles. - WEL: Laat animatie over aan Animate Elements To and From Top Layer, slepen aan Build a Draggable Element en reparenting aan Persistent Top Layer UI.
- Stel
displayniet onvoorwaardelijk in opdialog. Deze overschrijft de status Gesloten. Beperk het bereik metdialog[open].
Toegankelijkheid
- Voeg GEEN
role="dialog",aria-modal, handmatige focusval ofinerttoe die overschakelt naar een systeemeigen modaal venster. - Gebruik WEL een native
<button>met een toegankelijke naam voor sluitingsopties. - WEL: Gebruik één consistent kopniveau voor dialoogtitels.
Reservestrategieën
closedby (dialog-closedby)
Zonder closedby wordt een modaal nog steeds gesloten met Esc en het teruggebaar. Alleen klikken buiten het modale venster werkt niet meer, dus dit is graceful degradation. closedby="none" wordt op dezelfde manier genegeerd: Esc activeert cancel en sluit daarna het dialoogvenster. Houd de beveiliging voor niet-opgeslagen wijzigingen in een cancel-handler, niet alleen in closedby="none", zodat deze ook beschermt waar closedby ontbreekt. Als je Baseline-doel lichte afwijzing vereist, voeg je dit toe waar closedby ontbreekt:
if (!('closedBy' in HTMLDialogElement.prototype)) {
// Test the pointer position, not event.target: the <dialog> is also the target for its own
// padding and the gaps between its children, so a target check would close on those clicks
const outside = (e, dialog) => {
const r = dialog.getBoundingClientRect();
return e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom;
};
for (const dialog of document.querySelectorAll('dialog[closedby="any"]')) {
let pressedOutside = false;
dialog.addEventListener('pointerdown', (e) => { pressedOutside = outside(e, dialog); });
// Require the press and the release outside, so a text-selection drag that ends on the backdrop does not dismiss
dialog.addEventListener('pointerup', (e) => { if (pressedOutside && outside(e, dialog)) dialog.close(); });
}
}
overscroll-behavior
Behandel scroll-containment als progressive enhancement. Gebruik geen functiedetectie: @supports meldt de property als ondersteund, zelfs als niet-scrollbare containers deze negeren. Als dit niet wordt afgedwongen, bevat het dialoogvenster nog steeds scrollen wanneer de eigen content overloopt en maakt showModal() de pagina nog steeds inactief. Val niet terug op overflow: hidden of position: fixed op html of body, waardoor de hoofdscrollbar wordt verwijderd en de indeling wordt verschoven.
Aanroeperopdrachten (command / commandfor)
Als je Baseline-doel geen Invoker-opdrachten bevat, behoud je dezelfde opmaak en laad je de polyfill voorwaardelijk (dezelfde functionaliteit, als progressive enhancement). Detecteer de interface en laad deze nooit onvoorwaardelijk:
// MANDATORY: feature detect on HTMLButtonElement.prototype and load only when native support is missing
if (!('commandForElement' in HTMLButtonElement.prototype)) {
import('invokers-polyfill'); // npm install invokers-polyfill
}
Alleen regel 2 en 3 gebruiken Popover.
Fallbacks en browserondersteuning voor Popover
De Popover API is voornamelijk progressive enhancement, maar de bepalende gedragingen (promotie van de bovenste laag, licht sluiten en aanroepen van popovertarget) hebben geen equivalent dat alleen CSS gebruikt. Oudere browsers hebben een polyfill nodig, of een handmatige fallback als je er liever geen verzendt.
Polyfill: Als je het kenmerk popover wilt ondersteunen in oudere browsers, laad je @oddbird/popover-polyfill voorwaardelijk. VERPLICHT: Voer een functiedetectie uit door te controleren op de property popover op HTMLElement.prototype en laad de polyfill alleen als systeemeigen ondersteuning ontbreekt. Laad de polyfill NIET onvoorwaardelijk.
Met een bundler of importtoewijzing:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Zonder bundler importeer je vanuit een CDN in een <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
Opmerking over de stijl: De polyfill kan de echte pseudoklasse :popover-open niet definiëren, dus wordt in plaats daarvan de klasse .\:popover-open toegepast. VERPLICHT: Combineer de 2 met :is() of :where(), anders negeren browsers zonder :popover-open de hele regel:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Als alternatief kun je voor een verouderde fallback zonder polyfill position: fixed gebruiken en de coördinaten handmatig berekenen via getBoundingClientRect() of vertrouwen op de standaardpositionering met inset: auto als dat acceptabel is voor de use case.