{
 "$schema": "../../schema.json",
 "schema": "fundus/1",
 "id": "dialog-modal-nativ",
 "name": "Natives Modal mit <dialog>",
 "name_en": "Native modal <dialog> with closedby, ::backdrop and @starting-style",
 "kurz": "Modale Fenster aus dem nativen <dialog>: showModal(), closedby=\"any\" mit Klick-Fallback, ::backdrop und Ein- und Ausblenden per @starting-style, als Bildansicht, Bestätigung und Formular mit method=\"dialog\".",
 "kategorie": "Zugang & Robustheit",
 "kategorie_slug": "zugang",
 "kategorie_nr": 17,
 "bau_chat": "E",
 "prioritaet": "E",
 "trendstatus": "aufsteigend",
 "belegstatus": "teilbelegt",
 "website_belege": 1,
 "stimmung": [
  "ruhig",
  "präzise"
 ],
 "stimmung_roh": [
  "ruhig",
  "präzise",
  "funktional"
 ],
 "technik": [
  "dialog",
  "css",
  "vanilla-js",
  "form"
 ],
 "technik_gruppen": [
  "css",
  "js"
 ],
 "leistungskosten": "niedrig",
 "steckbrief": {
  "beschreibung": "1) Modale Fenster entstehen aus dem HTML-Element <dialog> und öffnen per showModal(). Der Browser legt sie in die oberste Ebene, macht den Rest der Seite inert, schließt mit Esc und bringt den Fokus beim Schließen zum Auslöser zurück. 2) closedby=\"any\" erlaubt zusätzlich das Schließen per Klick auf den abgedunkelten Hintergrund; wo der Browser das nicht kennt, übernimmt ein kleiner Klick-Fallback. 3) Ein- und Ausblenden laufen in CSS: @starting-style für den Startzustand, transition-behavior: allow-discrete für display und overlay, ::backdrop blendet mit. 4) Formulare im Dialog nutzen method=\"dialog\": Absenden schließt und liefert den Wert des Knopfs als returnValue.",
  "warum_es_wirkt": "Fokusfalle, Esc, inerter Hintergrund und Rückgabe des Fokus sind die Stellen, an denen selbst gebaute Modals scheitern. Das native Element bringt sie mit. Was bleibt, ist Gestaltung: ruhiges Ein- und Ausblenden, ein Hintergrund, der die Seite nicht verschluckt, und klare Knöpfe.",
  "wann_einsetzen": "Bestätigung vor einer folgenreichen Aktion, Bildansicht in Galerien und Projektlisten, kurze Formulare (Anfrage, Rückruf, Newsletter), Hinweise, die eine Entscheidung brauchen.",
  "wann_nicht": "Für Inhalte, die man neben der Seite lesen will (dann Seitenpanel ohne Modal oder eigene Seite). Für Menüs, Tooltips und Auswahllisten (dafür popover). Für lange Formulare mit mehreren Schritten (eigene Seite). Nicht beim Laden der Seite öffnen, ohne dass jemand es ausgelöst hat.",
  "varianten": [
   "Bildansicht: großes Bild mit Bildunterschrift „Titel • Ort • Jahr“, Blättern per Knopf und Pfeiltasten, Fokus auf Schließen",
   "Bestätigung: kurzer Text, zwei Knöpfe in <form method=\"dialog\">, Fokus auf der sicheren Wahl (Abbrechen)",
   "Formular: <form method=\"dialog\">, Ergebnis über returnValue, Abbrechen als type=\"button\"",
   "Auf dem Telefon als Blatt von unten (gleicher Code, andere Position und Startversatz)"
  ],
  "umsetzung": "Auslöser: <button type=\"button\"> mit dialog.showModal() (oder deklarativ commandfor=\"d\" command=\"show-modal\", wo unterstützt). <dialog id=\"d\" closedby=\"any\" aria-labelledby=\"d-titel\">. CSS: dialog { opacity: 0; translate: 0 12px; transition: opacity .2s, translate .2s, overlay .2s allow-discrete, display .2s allow-discrete } dialog[open] { opacity: 1; translate: 0; transition-duration: .3s } @starting-style { dialog[open] { opacity: 0; translate: 0 12px } }, ::backdrop genauso über background-color. Die @starting-style-Regel steht nach der [open]-Regel. Fallback ohne closedby: Klick auf das dialog-Element außerhalb seines Rechtecks ruft close(). Fokus: autofocus auf das erste sinnvolle Element (Abbrechen, erstes Feld, Schließen); im close-Ereignis zum Auslöser zurück, falls der Browser das nicht schon getan hat. Formular: <form method=\"dialog\">, Absenden mit value, Ergebnis im close-Ereignis über returnValue und FormData. Achtung: Enter in einem Feld löst den ersten Submit-Knopf im Formular aus. Abbrechen und × darum als type=\"button\" mit close() bauen oder hinter den Absende-Knopf stellen. Seite dahinter: html:has(dialog:modal) { overflow: hidden }. Für Vorschauen in iframes nicht automatisch showModal() aufrufen: Das holt den Fokus in den Rahmen.",
  "mobil": "Bildansicht füllt den Bildschirm, Formular und Bestätigung kommen als Blatt von unten mit großen Knöpfen. Schließen-Knopf immer sichtbar, nicht nur Wischen oder Hintergrund-Tippen. Light-Dismiss: Seit Chrome 154 schließen Dialog und Popover erst beim click, nicht mehr bei pointerdown. Eine Wischgeste zum Scrollen oder ein Rechtsklick auf den Hintergrund schließt den Dialog dort nicht mehr; in älteren Versionen konnte das schon passieren. Den eigenen Fallback ohne closedby deshalb ebenfalls auf click legen, nicht auf pointerdown (Chrome-154-Versionshinweise).",
  "reduzierte_bewegung": "Kein Versatz, nur kurzes Einblenden der Deckkraft (oder gar keine Transition). Funktion und Fokusführung bleiben gleich.",
  "barrierefreiheit": "Rolle und Modalität kommen vom Element: showModal() setzt den Rest der Seite inert, Esc schließt (WCAG 2.1.2 No Keyboard Trap ist erfüllt, weil Esc und ein Schließen-Knopf herausführen). 4.1.2 Name, Role, Value: aria-labelledby auf die Überschrift, optional aria-describedby auf den Text. 2.4.3 Focus Order: Fokus beim Öffnen auf das erste sinnvolle Element (autofocus), beim Schließen zurück zum Auslöser. 2.4.7 Focus Visible und 2.4.11 Focus Not Obscured (Minimum): Fokus im Dialog deutlich, nichts liegt darüber. 2.5.8 Target Size: Knöpfe mindestens 24 px, besser 44 px. 1.4.11 Non-text Contrast: Dialogrand oder Hintergrund hebt das Fenster mit 3:1 ab. 3.3.1 und 3.3.2 im Formular: Beschriftungen sichtbar, Fehler als Text am Feld, aria-invalid. Hintergrund-Klick ist Zusatz, nie der einzige Weg zum Schließen."
 },
 "messwerte": {
  "easing": "Öffnen cubic-bezier(0.1, 0.9, 0.3, 1), Schließen cubic-bezier(0.1, 0.6, 0.4, 1)",
  "dauer_ms": "Öffnen 300, Schließen 200 (KAAN); Chipperfield-Modals 150",
  "stagger_ms": "",
  "distanz": "eigene Festlegung: 12 px nach oben, Telefon-Blatt 32 px",
  "sonstiges": "Hintergrund blendet mit denselben Dauern; Rezept-Messung architektur-ruhiges-raster",
  "herkunft": "beobachtet",
  "quelle": "https://kaanarchitecten.com/dist/assets/app-hnm3wrp6.css"
 },
 "kurve": {
  "bezier": [
   0.1,
   0.9,
   0.3,
   1.0
  ],
  "css": "cubic-bezier(0.1, 0.9, 0.3, 1)",
  "name": "eigene Kurve",
  "dauer_ms": 300
 },
 "gesehen_bei": [
  {
   "seite": "Chrome for Developers: Light-dismiss a dialog",
   "url": "https://developer.chrome.com/docs/modern-web-guidance/guides/ui-behaviors/light-dismiss-a-dialog",
   "auszeichnung": "",
   "beobachtung": "closedby=\"any\" (Hintergrund-Klick, Esc, close()), \"closerequest\" (Standard bei Modals: Esc und close()), \"none\" (nur close()). Der Leitfaden nutzt es nur mit showModal(): nicht-modale Dialoge (show()) haben keinen Hintergrund für Light-Dismiss. Fallback ausdrücklich verlangt: Feature-Test 'closedBy' in HTMLDialogElement.prototype, sonst Klick auf das dialog-Element außerhalb seines Rechtecks als Hintergrund-Klick werten.",
   "art": "tutorial"
  },
  {
   "seite": "Chrome for Developers: Four new CSS features for smooth entry and exit animations",
   "url": "https://developer.chrome.com/blog/entry-exit-animations",
   "auszeichnung": "",
   "beobachtung": "Una Kravets und Joey Arhar: dialog[open] mit @starting-style als Startzustand, Ausgang über transition auf translate, overlay und display mit allow-discrete (Beispiel 0,7 s ease-out).",
   "art": "artikel"
  },
  {
   "seite": "web.dev: Building a dialog component",
   "url": "https://web.dev/articles/building/a-dialog-component",
   "auszeichnung": "",
   "beobachtung": "Adam Argyle: Mega- und Mini-Dialog, Formular mit method=\"dialog\", Knopf-Werte landen in returnValue, autofocus auf Abbrechen für bewusste Bestätigung, Light-Dismiss per Klick auf das dialog-Element.",
   "art": "tutorial"
  },
  {
   "seite": "KAAN Architecten",
   "url": "https://www.kaanarchitecten.com/",
   "auszeichnung": "",
   "beobachtung": "Laut Rezept-Messung (31.10.2026, app-hnm3wrp6.css): Modal rein .3s, raus .2s; Öffnen mit Ausklang-Kurve cubic-bezier(.1,.9,.3,1), Schließen mit Default-Kurve cubic-bezier(.1,.6,.4,1). Ob ein natives <dialog> dahinter steckt, ist nicht geprüft.",
   "art": "website"
  }
 ],
 "tutorials": [
  {
   "titel": "Chrome for Developers: Light-dismiss a dialog",
   "url": "https://developer.chrome.com/docs/modern-web-guidance/guides/ui-behaviors/light-dismiss-a-dialog"
  },
  {
   "titel": "Chrome for Developers: Four new CSS features for smooth entry and exit animations",
   "url": "https://developer.chrome.com/blog/entry-exit-animations"
  },
  {
   "titel": "web.dev: Building a dialog component",
   "url": "https://web.dev/articles/building/a-dialog-component"
  },
  {
   "titel": "Web Features Explorer: <dialog closedby>",
   "url": "https://web-platform-dx.github.io/web-features-explorer/features/dialog-closedby/"
  }
 ],
 "kombinierbar_mit": [
  "einblenden-starting-style",
  "galerie-shared-element-vollbild",
  "kontakt-anfrage-formular",
  "validierung-nach-interaktion",
  "button-zustaende",
  "token-swiss-knackig"
 ],
 "urls": {
  "detail": "https://cecodesign.de/lab/fundus/zugang/dialog-modal-nativ/",
  "meta": "https://cecodesign.de/lab/fundus/zugang/dialog-modal-nativ/meta.json",
  "demo": "https://cecodesign.de/lab/fundus/zugang/dialog-modal-nativ/demo"
 },
 "herkunft": {
  "quelldateien": [
   "DEF-Recherche 01.11.2026"
  ],
  "pruefnotizen": [
   "Interop 2026 (web.dev/blog/interop-2026, am 02.11.2026 gelesen): Dialoge und Popover mit closedby, :open und popover=\"hint\" sind Fokusbereich.",
   "Web Features Explorer am 01./02.11.2026: closedby in Chrome/Edge 134 und Firefox 141, Safari noch nicht (Baseline seit Juli 2025 durch Safari blockiert); Fallback nötig.",
   "Produktionsseite mit nativem <dialog> und closedby noch nicht selbst geprüft; KAAN liefert nur die Zeitwerte."
  ]
 },
 "bau": {
  "status": "fertig",
  "kurz": "Modale Fenster aus dem nativen <dialog>: showModal(), closedby=\"any\" mit Klick-Fallback, ::backdrop und Ein- und Ausblenden per @starting-style, als Bildansicht, Bestätigung und Formular mit method=\"dialog\".",
  "demo": true,
  "abhaengigkeiten": [],
  "werte": {
   "easing": "Öffnen cubic-bezier(0.1, 0.9, 0.3, 1) · Schließen cubic-bezier(0.1, 0.6, 0.4, 1) (KAAN / Chipperfield, Rezept architektur-ruhiges-raster)",
   "dauer_ms": "Öffnen 300 · Schließen 200 · Bildwechsel 250 (Ausblenden 150, dann Tausch) · Hover 250",
   "distanz": "translate 0 12px → 0; Telefon-Blatt 32px",
   "dialog_css": "dialog { opacity: 0; translate: 0 12px; transition: opacity .2s, translate .2s, overlay .2s allow-discrete, display .2s allow-discrete } dialog[open] { opacity: 1; translate: 0; transition-duration: .3s } @starting-style { dialog[open] { opacity: 0; translate: 0 12px } }",
   "backdrop": "::backdrop rgb(31 35 35 / 0) → rgb(31 35 35 / .56), Bildansicht rgb(18 20 20 / .92), gleiche Dauern und Kurven",
   "schliessen": "closedby=\"any\"; Fallback ohne 'closedBy' in HTMLDialogElement.prototype: Klick mit target === dialog außerhalb von getBoundingClientRect() → close('hintergrund'); Esc nativ; sichtbarer Schließen-Knopf 44 × 44 px",
   "fokus": "autofocus: Bildansicht auf Schließen, Bestätigung auf Abbrechen, Formular auf erstes Feld; im close-Ereignis Fokus zurück zum Auslöser, falls er auf body oder im Dialog liegt; Fokusring 2px #0022ff, Versatz 3px (auf dunklem Grund #8fa3ff)",
   "formular": "<form method=\"dialog\" novalidate>, Absenden value=\"senden\" → returnValue; Abbrechen und × als type=\"button\" mit close('abbrechen'); Pflichtfelder mit aria-invalid und Fehlertext per aria-describedby, Fokus auf erstes fehlerhaftes Feld",
   "scroll": "html:has(dialog:modal) { overflow: hidden }, scrollbar-gutter: stable gegen Springen",
   "vorfuehrung": "öffnet per open-Attribut mit nachgebildetem Hintergrund und Fokusring, damit die Galerie-Vorschau keinen Fokus stiehlt"
  },
  "ausloeser": "klick",
  "touch": "Formular und Bestätigung kommen unter 640 px als Blatt von unten (volle Breite, max. 92svh, Startversatz 32px), die Knopfleiste klebt unten und die Knöpfe teilen sich die Breite. Bildansicht fast bildschirmbreit, Schließen-Knopf über dem Bild. Hover-Zustände entfallen. Die Hauptnavigation steht unter 900 px als zweite Zeile unter der Marke.",
  "reduziert": "Kein Versatz (--versatz 0), Öffnen 120 ms, Schließen 80 ms nur über die Deckkraft; Bildwechsel ohne Überblendung. Fokusführung und Schließwege unverändert.",
  "korrekturen": {
   "umsetzung": "Auslöser: <button type=\"button\"> mit dialog.showModal() (oder deklarativ commandfor=\"d\" command=\"show-modal\", wo unterstützt). <dialog id=\"d\" closedby=\"any\" aria-labelledby=\"d-titel\">. CSS: dialog { opacity: 0; translate: 0 12px; transition: opacity .2s, translate .2s, overlay .2s allow-discrete, display .2s allow-discrete } dialog[open] { opacity: 1; translate: 0; transition-duration: .3s } @starting-style { dialog[open] { opacity: 0; translate: 0 12px } }, ::backdrop genauso über background-color. Die @starting-style-Regel steht nach der [open]-Regel. Fallback ohne closedby: Klick auf das dialog-Element außerhalb seines Rechtecks ruft close(). Fokus: autofocus auf das erste sinnvolle Element (Abbrechen, erstes Feld, Schließen); im close-Ereignis zum Auslöser zurück, falls der Browser das nicht schon getan hat. Formular: <form method=\"dialog\">, Absenden mit value, Ergebnis im close-Ereignis über returnValue und FormData. Achtung: Enter in einem Feld löst den ersten Submit-Knopf im Formular aus. Abbrechen und × darum als type=\"button\" mit close() bauen oder hinter den Absende-Knopf stellen. Seite dahinter: html:has(dialog:modal) { overflow: hidden }. Für Vorschauen in iframes nicht automatisch showModal() aufrufen: Das holt den Fokus in den Rahmen.",
   "varianten": [
    "Bildansicht: großes Bild mit Bildunterschrift „Titel • Ort • Jahr“, Blättern per Knopf und Pfeiltasten, Fokus auf Schließen",
    "Bestätigung: kurzer Text, zwei Knöpfe in <form method=\"dialog\">, Fokus auf der sicheren Wahl (Abbrechen)",
    "Formular: <form method=\"dialog\">, Ergebnis über returnValue, Abbrechen als type=\"button\"",
    "Auf dem Telefon als Blatt von unten (gleicher Code, andere Position und Startversatz)"
   ]
  },
  "geprueft": "02.11.2026",
  "notiz": "Welt: Rezept architektur-ruhiges-raster, erfundenes Büro „Marktgasse Architekten“ in Feldkirch. Hanken Grotesk 400/450/500 mit Kursiv statt Fett, Weiß, Hellgrau #e1e3e3, Text #231f20, Meta #76767a bzw. #5f6063 auf Grau, einziger Interaktionston #0022ff für Fokus und Markierung. Drei eigene, mit Python/numpy gerenderte Projektbilder (medien/dialog-modal-nativ-haus|schule|turm.jpg) und ein eigener SVG-Stadtplan. Grundvariante ist die Bildansicht, weil sie im Rezept am häufigsten vorkommt; Bestätigung ist die Zustimmung zur externen Karte, Formular die Projektanfrage. Die Vorführung öffnet die Dialoge per open-Attribut mit nachgebildetem Hintergrund, damit die Vorschau im iframe der Galerie keinen Fokus an sich zieht; bei echter Bedienung läuft immer showModal(). Pult zeigt, ob closedby nativ oder per Fallback läuft. Referenzen: developer.chrome.com, web.dev, web-platform-dx.github.io, kaanarchitecten.com (nur Zeitwerte). AD-Review 02.11.2026: Note 4. Nachgebessert: Hauptnavigation unter 900 px als zweite Zeile statt versteckt, Bestätigung als role=\"alertdialog\". Korrektur 02.11.2026: Steckbrief (mobil) um Light-Dismiss ab Chrome 154 ergänzt (click statt pointerdown, Scrollen schließt nicht mehr; Chrome-Versionshinweise per Abruf geprüft). Fallback der Demo nutzt schon click.",
  "varianten": [
   {
    "id": "bild",
    "name": "Bildansicht",
    "wann": "Projektlisten, Galerien und Referenzen bei Architektur, Fotografie, Handwerk und Immobilien: großes Bild mit Bildunterschrift und Blättern.",
    "werte": {
     "backdrop": "rgb(18 20 20 / .92)",
     "fokus_start": "Schließen",
     "blaettern": "Knöpfe und Pfeiltasten links/rechts, Titel in aria-live=\"polite\", Bildwechsel 250 ms",
     "breite": "min(1240px, 100vw − 48px)"
    }
   },
   {
    "id": "bestaetigung",
    "name": "Bestätigung",
    "wann": "Vor folgenreichen oder datenschutzrelevanten Aktionen, z. B. externe Karte oder Video laden, Merkliste leeren, Termin absagen.",
    "werte": {
     "backdrop": "rgb(31 35 35 / .56)",
     "fokus_start": "Abbrechen (sichere Wahl)",
     "rueckgabe": "<form method=\"dialog\">, returnValue laden | abbrechen",
     "breite": "min(560px, 100vw − 32px)"
    }
   },
   {
    "id": "formular",
    "name": "Formular",
    "wann": "Kurze Anfragen, Rückruf oder Newsletter mit wenigen Feldern, wenn der Kontext der Seite erhalten bleiben soll.",
    "werte": {
     "backdrop": "rgb(31 35 35 / .56)",
     "fokus_start": "erstes Feld (Name)",
     "rueckgabe": "returnValue senden, FormData im close-Ereignis",
     "abbrechen": "type=\"button\" + close('abbrechen')",
     "telefon": "Blatt von unten, Versatz 32px"
    }
   }
  ]
 }
}
