Dialog
Klassen settes på et <dialog>, og resten er nettleserens eget element. Har appen din JavaScript i nettleseren, trenger du ingenting fra Fristil: åpne dialogen med showModal(), lukk den med close().
import "@fristil/designsystem/tokens.css"import "@fristil/designsystem/dialog.css"Å åpne den må være et kall. showModal() flytter fokus inn, holder fokus inne i dialogen, lukker på Escape og gjør resten av siden utilgjengelig. Et <dialog open> i markupen gjør ingen av delene, og er bare en boks på siden. Det finnes ingen deklarativ måte å åpne en modal dialog på, verken i Fristil eller i HTML.
Det er et problem bare for én slags app: den som ikke har noe sted å kalle fra, fordi all markup blir til på serveren. Til den finnes <fs-dialog>, lenger ned på siden. Den er valgfri, på samme måte som <fs-field> er valgfri ved siden av fs.field().
Slik ser den ut
Section titled “Slik ser den ut”Forhåndsvisningen viser dialogen slik den ser ut når den er åpen. En ekte <dialog> ligger i topplaget, og tvinges her ned i boksen for å kunne vises:
Slette søknaden?
Søknaden og vedleggene blir borte. Dette kan ikke angres.
<div class="fs-dialog"><h2 class="fs-dialog__title">Slette søknaden?</h2><div class="fs-dialog__body"> <p>Søknaden og vedleggene blir borte. Dette kan ikke angres.</p></div><div class="fs-dialog__footer"> <button class="fs-button" data-variant="secondary" type="button">Avbryt</button> <button class="fs-button" data-variant="danger" type="button">Slett søknaden</button></div></div>Komplett eksempel
Section titled “Komplett eksempel”<button class="fs-button" data-variant="danger" id="apne" type="button"> Slett søknaden</button>
<dialog class="fs-dialog" id="bekreft" aria-labelledby="bekreft-tittel"> <h2 class="fs-dialog__title" id="bekreft-tittel">Slette søknaden?</h2> <div class="fs-dialog__body"> <p>Søknaden og vedleggene blir borte. Dette kan ikke angres.</p> </div> <form method="dialog" class="fs-dialog__footer"> <button class="fs-button" data-variant="secondary" value="avbryt">Avbryt</button> <button class="fs-button" data-variant="danger" value="slett">Slett søknaden</button> </form></dialog>
<script type="module"> const dialog = document.getElementById("bekreft")
document.getElementById("apne").addEventListener("click", () => { dialog.showModal() })
dialog.addEventListener("close", () => { if (dialog.returnValue === "slett") { // Slett søknaden her } })</script><form method="dialog"> lukker dialogen når en knapp trykkes, og legger knappens value i dialog.returnValue. Da trenger du ingen lyttere på knappene, og Enter virker som forventet.
Escape lukker dialogen av seg selv. Skal du hindre det, for eksempel midt i en lagring, avbryt cancel-hendelsen. Gjør det sjelden: en dialog som ikke kan lukkes er en felle.
Prøv den
Section titled “Prøv den”Knappen her gjør det en server ellers ville gjort: setter open på <fs-dialog>. Komponenten kaller showModal(), og dialogen legger seg i topplaget med fokus inne i seg. Skriver du markupen i JavaScript, kaller du showModal() selv, og trenger ingen <fs-dialog> rundt.
Når serveren åpner dialogen
Section titled “Når serveren åpner dialogen”Dette avsnittet gjelder bare apper uten egen kode i nettleseren. Har du JavaScript der, hopp over det: showModal() er ett kall, og en komponent i mellom gir deg ingenting.
En app i Datastar, htmx, en Go-mal eller en Razor-visning har ikke det stedet. Den sender open på <fs-dialog>, og komponenten gjør kallet.
import { defineFsDialog } from "@fristil/designsystem/dialog"
defineFsDialog()<fs-dialog open> <dialog class="fs-dialog" aria-labelledby="kvittering-tittel" open> <h2 class="fs-dialog__title" id="kvittering-tittel">Vedtaket er registrert</h2> <div class="fs-dialog__body"> <p>Saken er ferdigbehandlet og sendt til arkiv.</p> </div> <form method="dialog" class="fs-dialog__footer"> <button class="fs-button" value="lukk">Lukk</button> </form> </dialog></fs-dialog>Gi knappen som lukker dialogen en value. Uten JavaScript er dialogen en boks på siden, og <form method="dialog"> lukker den før noe skript har kjørt. Komponenten kjenner igjen det som alt har skjedd på returnValue, som nettleseren setter til verdien på knappen. Er den tom, ser komponenten en vert som sier «åpen» og en lukket dialog, og åpner den igjen.
Lukker brukeren dialogen, med Escape eller med en knapp i <form method="dialog">, fjerner komponenten open fra <fs-dialog> igjen, slik at markupen sier det samme som skjermen. Et klikk på flaten bak lukker den ikke; det gjør heller ikke en vanlig <dialog>. Komponenten melder fra med hendelsen dialog-toggle, som har detail.open.
De to open-ene i eksempelet er ikke det samme:
| Hvor | Hvem setter den | Hva som skjer i en oppdatering |
|---|---|---|
På <fs-dialog> | Serveren, når dialogen skal vises | Serveren bestemmer. Sender den open på nytt, åpnes dialogen igjen |
På <dialog> | Serveren når dialogen skal vises, og nettleseren i showModal() | Komponenten setter det tilbake så lenge dialogen står i topplaget |
server-controlled på <fs-dialog> slår av den siste. Da bestemmer hver oppdatering om dialogen vises, også når den står i topplaget.
At verten følger serveren er et valg og ikke en forglemmelse. Sender serveren området på nytt med open fortsatt satt, åpnes dialogen altså igjen; skal en avvisning vare, må appen si fra til serveren, som for all annen tilstand serveren eier.
Attributtet på selve <dialog> er noe annet: nettleseren setter det i showModal(), og serveren sendte det ikke, så en oppdatering river det bort og skjuler dialogen i det øyeblikket den åpnet den. Komponenten setter det tilbake når dialogen fortsatt står i topplaget, så malen din trenger ingenting ekstra.
Serveren skriver det likevel når dialogen skal vises, og fs.dialog({ titleId, open: true }) gir det på begge elementene. Grunnen er at en <dialog> uten open er skjult: uten JavaScript fantes ikke innholdet serveren ville vise, i det hele tatt. Med attributtet vises det, plassert over innholdet under seg og uten flate bak, slik nettleserens egen stil gjør det. Det er ikke like pent som en modal, men det er der, og komponenten gjør den om til en ekte modal i det skriptet kjører.
Det siste gjelder også deg som har JavaScript: dialogen vises først der den står i flyten, og hopper til midten når modulen har kjørt. På en tung side er det et synlig hopp, ikke ett bilde. Vil du unngå det, la serveren sende dialogen lukket og åpne den fra klienten i stedet.
TypeScript
Section titled “TypeScript”import { fs } from "@fristil/designsystem/react"
const boks = fs.dialog({ titleId: "kvittering-tittel", open: true })
<fs-dialog {...boks.host}> <dialog {...boks.dialog}> <h2 {...boks.title}>Vedtaket er registrert</h2> <div {...boks.body}>Saken er ferdigbehandlet og sendt til arkiv.</div> <form method="dialog" {...boks.footer}> <button {...fs.button()} value="lukk">Lukk</button> </form> </dialog></fs-dialog>Importen går til /react her, siden eksempelet er JSX. Derfra heter nøklene className og htmlFor. Skriver du markupen i noe annet, importer @fristil/designsystem og få class og for.
fs.dialog.title, fs.dialog.body og fs.dialog.footer gir klassenavnene alene. Se Typesikker bruk.
Lager du markupen med JavaScript og styrer alt i nettleseren, trenger du ingen komponent: kall showModal() selv, som i «Komplett eksempel» over. Da skal du ikke sende open inn i fs.dialog(). Attributtet er serverens beskjed til <fs-dialog>, og showModal() kaster InvalidStateError på en dialog som alt står åpen.
I React er attributtet dessuten det eneste som stemmer når serveren rendrer dialogen åpen. Komponenten setter open på <dialog> før React hydrerer, så sto det ikke i serverens HTML, meldte React avvik ved hvert eneste oppslag.
Klasser du kan style
Section titled “Klasser du kan style”| Klasse | Hva det er |
|---|---|
.fs-dialog | Selve dialogen |
.fs-dialog__title | Overskriften |
.fs-dialog__body | Innholdet, som ruller når dialogen blir for høy |
.fs-dialog__footer | Raden med knapper nederst |
| Variabel | Standard |
|---|---|
--fs-dialog-width | min(32rem, 100%, calc(100vw - var(--size-8))) |
--fs-dialog-padding | var(--size-5) |
--fs-dialog-radius | var(--size-2) |
100% i bredden gjelder når dialogen ikke ligger i topplaget. En <dialog> åpnet med showModal() forholder seg til vindusruten uansett, men lager du en boks på siden av den, som i forhåndsvisningen over, ville 100vw gjort den bredere enn boksen.
Flaten bak dialogen er ::backdrop, og finnes bare når dialogen er åpnet med showModal(). Fargen kommer fra --semantic-overlay-backdrop og er kraftigere i mørkt tema, der en sort flate ellers ville vært usynlig.
Se Tilpasning for hvordan variablene og laget virker.
Tilgjengelighet
Section titled “Tilgjengelighet”- Dialogen trenger et navn.
fs.dialog({ titleId })skriveraria-labelledbymot overskriften. Skriver du markupen selv og ikke har noen overskrift, brukaria-labeli stedet. - Overskriften skal stille spørsmålet: «Slette søknaden?», ikke «Bekreft».
- Knappeteksten skal si hva som skjer. «Slett søknaden» er tydeligere enn «OK», særlig for den som hører knappen uten å se overskriften.
- Etter at dialogen lukkes, gir nettleseren fokus tilbake til knappen som åpnet den. Flytter du fokus selv i mellomtiden, må du sette det tilbake.
- Ikke bruk en dialog til noe som kunne stått på siden. Den tar over hele skjermen for den som bruker forstørrelse.
<fs-dialog>åpner alltid modalt. Etshow()ville gitt en boks som ser lik ut, men som ikke fanger fokus og ikke lukker på Escape. Forskjellen er:modal, og den er testet i alle tre nettlesermotorene.