Modal
Auf dieser Seite
Ein Modal ist ein popover-Element. Jeder Button, dessen popovertarget zur id des Modals passt, öffnet es — ganz ohne Skript, samt Hintergrund, Schließen bei Klick nach außen, Escape und Fokusverwaltung, die alle dem Browser gehören.
Deshalb ist die id Pflicht, und deshalb wirft die Komponente ohne sie: die id ist die ganze Verkabelung.
Einfaches Modal
Jedes Modal auf dieser Seite geht wirklich auf — probier es aus.
Website neu bauen?
fragment(
button({ popovertarget: 'demo-basic' }, 'Modal öffnen'),
modal({ id: 'demo-basic', title: 'Website neu bauen?' },
'Das führt sitelo build aus und veröffentlicht dist/ erneut.',
),
)Mit Footer
Ein Schließen-Button ist jeder Button, der mit popovertargetaction="hide" auf dieselbe id zeigt.
Diese Seite löschen?
fragment(
button({ color: 'danger', popovertarget: 'demo-confirm' }, 'Seite löschen…'),
modal({
id: 'demo-confirm',
title: 'Diese Seite löschen?',
footer: stack({ direction: 'row', gap: 'sm' },
button({
variant: 'ghost',
color: 'neutral',
popovertarget: 'demo-confirm',
popovertargetaction: 'hide',
}, 'Abbrechen'),
button({ color: 'danger' }, 'Löschen'),
),
}, 'Das lässt sich nicht rückgängig machen. Das erzeugte HTML verschwindet beim nächsten Build.'),
)Größen
Klein
Mittel
Groß
fragment(
stack({ direction: 'row', gap: 'sm', wrap: true },
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-sm' }, 'Klein'),
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-md' }, 'Mittel'),
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-lg' }, 'Groß'),
),
modal({ id: 'demo-sm', size: 'sm', title: 'Klein' }, 'size: sm — etwa 24rem.'),
modal({ id: 'demo-md', title: 'Mittel' }, 'Der Standard — etwa 32rem.'),
modal({ id: 'demo-lg', size: 'lg', title: 'Groß' }, 'size: lg — etwa 48rem.'),
)Formulare in einem Modal
Neue Seite
fragment(
button({ variant: 'soft', popovertarget: 'demo-form' }, 'Neue Seite…'),
modal({
id: 'demo-form',
title: 'Neue Seite',
footer: stack({ direction: 'row', gap: 'sm' },
button({ variant: 'ghost', color: 'neutral', popovertarget: 'demo-form', popovertargetaction: 'hide' }, 'Abbrechen'),
button({ type: 'submit' }, 'Anlegen'),
),
},
stack({ gap: 'md' },
textField({ label: 'Titel', name: 'modal-title', placeholder: 'Über' }),
selectField({ label: 'Endung', name: 'modal-ext', options: ['.ht.js', '.ht.ts', '.ht.jsx'] }),
),
),
)Ohne Schließen-Button
closable: false lässt das × in der Ecke weg. Escape und ein Klick nach außen schließen es weiterhin — ein Popover lässt sich nicht wirklich blockierend machen, und das ist ohnehin meist das richtige Verhalten.
Drück Escape
fragment(
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-bare' }, 'Ohne Schließen-Button'),
modal({ id: 'demo-bare', title: 'Drück Escape', closable: false },
'Oder klick irgendwo außerhalb dieses Dialogs.',
),
)Langer Inhalt
Der Körper scrollt; Kopf und Fuß bleiben stehen.
Release Notes
Änderung 1 — etwas wurde behoben.
Änderung 2 — etwas wurde behoben.
Änderung 3 — etwas wurde behoben.
Änderung 4 — etwas wurde behoben.
Änderung 5 — etwas wurde behoben.
Änderung 6 — etwas wurde behoben.
Änderung 7 — etwas wurde behoben.
Änderung 8 — etwas wurde behoben.
Änderung 9 — etwas wurde behoben.
Änderung 10 — etwas wurde behoben.
Änderung 11 — etwas wurde behoben.
Änderung 12 — etwas wurde behoben.
fragment(
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-long' }, 'Langes Modal'),
modal({
id: 'demo-long',
title: 'Release Notes',
footer: button({ popovertarget: 'demo-long', popovertargetaction: 'hide' }, 'Schließen'),
},
stack({ gap: 'md' },
...Array.from({ length: 12 }, (unused, index) =>
text({ variant: 'small', tone: 'muted' }, 'Änderung ' + (index + 1) + ' — etwas wurde behoben.'),
),
),
),
)Scrollen im Hintergrund
Die Seite hinter einem offenen Modal scrollt nicht. Das ist das Einzige, was die Popover-API dir überlässt, und es passiert hier in CSS — kein Skript, nichts zu initialisieren. Übergib lockScroll: false, damit der Hintergrund wie gewohnt scrollt.
Browser-Unterstützung
Die Popover-API gibt es in jedem aktuellen Browser. In einem zu alten, der sie nicht kennt, rendert das Modal inline in der Seite statt darüber: sichtbar und benutzbar, nur eben nicht überlagert. Nichts verschwindet.
Props
| Prop | Typ | Standard | Beschreibung |
|---|---|---|---|
id | string | — | Pflicht. Worauf das popovertarget eines Auslösers zeigt. |
title | Child | — | Überschrift und zugänglicher Name des Dialogs. |
size | 'sm' | 'md' | 'lg' | 'md' | Maximale Breite. |
footer | Child | — | Untere Reihe, auf einem eigenen getönten Band. |
closable | boolean | true | Das × in der Kopfzeile zeigen. |
closeLabel | string | 'Close' | Zugänglicher Name dieses Buttons. |
lockScroll | boolean | true | Die Seite dahinter am Scrollen hindern, solange es offen ist. |
closeButton({ target }) rendert dieses × für sich, für eine Kopfzeile, die du selbst baust.