Skip to main content

Modal

Modal brukes til å vise innhold oppå eksisterende side og krever at brukeren forholder seg til innholdet før de kan fortsette. Komponenten fungerer som en tom container som fylles med innhold basert på behov. Den bygger på det native <dialog>-elementet, så fokushåndtering, tastatur og skjermleser fungerer uten ekstra oppsett.

Egnet til

  • Kritiske eller viktige handlinger (f.eks. bekreftelser)
  • Skjema eller oppgaver som krever fokus
  • Midlertidig innhold som ikke hører hjemme i hovedflyten
  • Situasjoner der brukeren må ta stilling før de går videre

Uegnet til

  • Ikke-kritisk informasjon (bruk inline innhold eller Accordion)
  • Lange eller komplekse prosesser
  • Innhold som brukeren trenger å referere til samtidig som resten av siden
  • Gjentatt bruk i samme flyt (kan skape frustrasjon)

Bruk

En modal kan brukes kontrollert (du styrer open) eller ukontrollert (dialogen eier tilstanden selv) — se Kontrollert og ukontrollert bruk. Uansett lukker den seg på Escape, lukk-knapp eller klikk på bakgrunnen. Klikk på bakgrunnen lukker som standard — slå det av for skjemaer og destruktive handlinger. Gi den alltid en tittel som navngir dialogen.

Hold open i state og sett den til false fra onOpenChange. Modalen er satt sammen av underkomponenter i Radix-stil: Modal.Header, Modal.Title, Modal.Body, Modal.Footer og Modal.ButtonGroup. Klikk på bakgrunnen lukker også dialogen som standard. Klikk på knappen for å prøve.

Laster...

Eksempler

Kontrollert og ukontrollert bruk

Modalen kan brukes på to måter.

Kontrollert (React): du eier åpen-tilstanden selv i useState og speiler den inn via open. onOpenChange kalles når dialogen ber om å bli lukket (Escape, lukk-knapp, bakgrunnsklikk), og du setter open til false som respons. Dette er én sannhetskilde — bruk det når noe utenfor dialogen skal kunne styre den (åpne fra flere steder, lukke etter et API-kall, speile til URL).

function Kontrollert() {
const [open, setOpen] = useState(false);

return (
<>
<Button aria-haspopup="dialog" onClick={() => setOpen(true)}>
Åpne
</Button>

<Modal open={open} onOpenChange={setOpen}>
<Modal.Header>
<Modal.Title>Kontrollert dialog</Modal.Title>
<Modal.CloseButton label="Lukk" />
</Modal.Header>
<Modal.Body>
<Modal.Description>Du eier open-tilstanden selv.</Modal.Description>
</Modal.Body>
</Modal>
</>
);
}

Ukontrollert (React): utelat open. Da eier Modal tilstanden selv og lukker seg via Escape, lukk-knapp og bakgrunnsklikk uten at du holder noe state. defaultOpen styrer startverdien (f.eks. en dialog som skal være åpen med én gang siden lastes), og onOpenChange er valgfri — den fungerer da kun som en observatør. Bruk dette når du ikke trenger å åpne dialogen på nytt fra utsiden.

function Ukontrollert() {
return (
<Modal defaultOpen onOpenChange={(open) => console.log('åpen:', open)}>
<Modal.Header>
<Modal.Title>Ukontrollert dialog</Modal.Title>
<Modal.CloseButton label="Lukk" />
</Modal.Header>
<Modal.Body>
<Modal.Description>
Åpen fra start; Modal eier tilstanden og lukker seg selv.
</Modal.Description>
</Modal.Body>
</Modal>
);
}

Skal en trigger-knapp åpne dialogen uten at du holder state, er ren HTML riktig verktøy: det native <dialog> åpnes med showModal() via data-modal-open og eier sin egen tilstand — nettleseren lukker på Escape og bakgrunnsklikk. Se HTML-fanen under «Bruk».

Slå av bakgrunnslukking

Klikk på bakgrunnen lukker dialogen som standard. For skjemaer med ulagrede data og destruktive handlinger vil du slå det av, så et utilsiktet klikk ikke gir datatap. I React setter du closeOnBackdropClick={false}; i ren HTML legger du data-no-close-on-backdrop<dialog>.

Laster...
Slå av bakgrunnslukking for skjemaer og destruktive handlinger

Et utilsiktet klikk utenfor dialogen lukker den umiddelbart. For skjemaer med ulagrede data eller bekreftelse av noe farlig betyr det tap av arbeid. Sett closeOnBackdropClick={false} (React) eller data-no-close-on-backdrop (HTML) i disse tilfellene.

Retningslinjer

Åpne modal bare når brukeren må svare først

En modal stopper alt annet: bakgrunnen er utilgjengelig, og fokus er låst inne. Det er riktig når svaret må gis før flyten kan gå videre, og feil når innholdet like godt kunne stått på siden. Trenger du bare å vise litt mer, bruk Popover eller ReadMore.

Åpne med en tittel som sier hvorfor

Modal.Title er det første en skjermleser leser opp, og det første øyet finner. Skriv den som et svar på «hvorfor kom dette opp?». Trenger svaret en setning mer, legg den i Modal.Description — da kobles den automatisk som beskrivelse av dialogen.

Hold innholdet fokusert

Én oppgave per modal. Blir innholdet så mye at du strekker deg mot size="large" eller "full", er det et tegn på at dette hører på en egen side. Velg size etter hvor mye innholdet faktisk trenger, ikke etter hvor viktig saken er.

Gi én primær handling og én vei tilbake

Én knapp fører oppgaven i mål, og én lar brukeren komme seg ut. Flere likestilte knapper gjør at brukeren må lese alle før hen tør trykke. Lukkeknappen (Modal.CloseButton med label) skal alltid være der, i tillegg til Escape.

Send brukeren tilbake dit hen kom fra

Når modalen lukkes, skal brukeren stå på samme sted i flyten som da den åpnet, med samme rulleposisjon og samme skjemadata. Komponenten flytter fokus tilbake til trigger-knappen, men tilstanden i siden er ditt ansvar.

Ikke åpne modal over modal

To lag med fanget fokus gir brukeren ingen holdepunkt for hvor hen er eller hva Escape lukker. Trenger du et steg videre, bytt innholdet i den samme modalen.

Designvalg

Escape lukker alltid dialogen, og det er bevisst: fokus er fanget inne i en modal, og Escape er den forventede rømningsveien (WCAG 2.1.2, ingen tastaturfelle). Noen skjema-flyter vil hindre utilsiktet Escape-lukking, på samme måte som med bakgrunnsklikk. Vi innfører det ikke, fordi å fjerne Escape uten en annen tydelig rømningsvei svekker tilgjengeligheten. Trenger du å bremse en utilsiktet lukking, bruk en bekreftelses-dialog framfor å blokkere tasten.

Universell utforming

Hva du selv må sørge for

  • Gi tydelig tittel og struktur. Bruk Modal.Title (eller aria-label<dialog> i ren HTML) og gjerne Modal.Description. Tittelen navngir dialogen for skjermlesere, og en tydelig struktur med tittel, beskrivelse og handlinger gjør at hjelpemidler forstår at brukeren har gått inn i en ny kontekst. Uten tittel er dialogen unavngitt. Komponenten gir role dialog fra det native <dialog>-elementet.
  • Oversett all tekst: tittel, label på lukk-knappen og knappetekster kommer fra deg og må være på brukerens språk (bokmål, nynorsk, engelsk). Komponenten har ingen hardkodet tekst.
  • Merk triggerknappen. Sett aria-haspopup="dialog" på knappen som åpner dialogen (og gjerne aria-controls som peker til dialogens id i ren HTML), så skjermlesere annonserer at den åpner en dialog.
  • Ikke stol på farge alene. Viktig informasjon og handlinger i modalen må ikke kommuniseres med farge alene. Det må være tydelig gjennom tekst og struktur.
  • Vær varsom med bakgrunnslukking. Bakgrunnslukking er på som standard; slå den av med closeOnBackdropClick={false} / data-no-close-on-backdrop for skjemaer eller destruktive handlinger der et uhell gir datatap.
  • Hold det viktigste synlig. Ved mye innhold må den sentrale informasjonen og handlingene være synlige før brukeren må scrolle. På små skjermer kan size="full" gi innholdet hele flaten i stedet for å klemme det inn i en boks.

Hva komponenten gjør automatisk

  • Fokus-trap: mens dialogen er åpen holdes fokus inne i den (native <dialog> åpnet med showModal()). Tab og Shift+Tab sykler kun mellom elementene i dialogen.
  • Åpningsfokus på dialogen: ved åpning settes fokus på selve dialogen, ikke på lukk-knappen. Skjermlesere annonserer da «<tittel>, dialog», og Tab flytter videre til første interaktive element. Vil du heller fokusere et bestemt element (f.eks. første felt i et skjema), sett autofocus på det elementet inni modalen.
  • Escape lukker: Escape ber om lukking (via onOpenChange / data-modal-close-atferd), som forventet av en modal dialog.
  • Fokus-retur: når dialogen lukkes, føres fokus tilbake til elementet som åpnet den.
  • Inert bakgrunn: innholdet bak dialogen gjøres utilgjengelig for både mus og skjermleser (aria-modal-atferd fra det native elementet).
  • Top-layer: dialogen tegnes over alt annet innhold uten at du trenger å styre z-index.
  • Fokusindikator: tydelig fokusring (:focus-visible) på lukk-knappen og andre interaktive elementer.
  • Touch-mål: lukk-knappen er minst 44 × 44 px.
  • Dempet bevegelse: inn-animasjonen er kort og slås av ved prefers-reduced-motion.

Tastaturnavigasjon

TastHandling
TabFlytter fokus til neste fokuserbare element i dialogen; sykler tilbake til første når slutten nås (fokus-trap)
Shift+TabFlytter fokus til forrige fokuserbare element i dialogen; sykler til siste fra første
EscapeLukker dialogen og fører fokus tilbake til elementet som åpnet den
Enter / SpaceAktiverer knappen som har fokus (lukk, avbryt, bekreft osv.)

Skjermleser

  • Ved åpning annonseres dialogen med tittel og rolle, f.eks. «Bekreft sletting, dialog»
  • Fokus settes inne i dialogen, og innholdet bak gjøres inert (utilgjengelig for skjermleser)
  • Lukk-knappen annonseres med sitt tilgjengelige navn (label), f.eks. «Lukk, knapp»
  • Ved lukking føres fokus tilbake til triggerknappen som åpnet dialogen

WCAG-kriterier

Sist gjennomgått: 2026-07-07 — alle 56 WCAG 2.2-kriterier vurdert

WCAG-kriterier7 ditt ansvar · 16 håndtert · 34 ikke relevant · 0 ikke på plass
Ditt ansvar (7)
KriteriumNivåHva du må gjøre
4.1.2 Navn, rolle, verdiAGi alltid dialogen en tittel. Bruk Modal.Title (eller aria-label på <dialog> i ren HTML) slik at dialogen får et tilgjengelig navn. Modal.Title kobles automatisk til dialogen via aria-labelledby. Uten tittel annonserer skjermlesere kun «dialog» uten kontekst. Skriv en kort tittel som beskriver oppgaven.
1.3.1 Informasjon og relasjonerAGi alltid dialogen en tittel. Bruk Modal.Title (eller aria-label på <dialog> i ren HTML) slik at dialogen får et tilgjengelig navn. Modal.Title kobles automatisk til dialogen via aria-labelledby. Uten tittel annonserer skjermlesere kun «dialog» uten kontekst. Skriv en kort tittel som beskriver oppgaven.
2.4.6 Overskrifter og ledeteksterAAGi alltid dialogen en tittel. Bruk Modal.Title (eller aria-label på <dialog> i ren HTML) slik at dialogen får et tilgjengelig navn. Modal.Title kobles automatisk til dialogen via aria-labelledby. Uten tittel annonserer skjermlesere kun «dialog» uten kontekst. Skriv en kort tittel som beskriver oppgaven.
3.1.2 Språk på deler av innholdAAOversett all tekst i dialogen. Tittel, label på Modal.CloseButton (aria-label) og all knappetekst kommer fra deg og må være på brukerens språk (bokmål, nynorsk, engelsk). Komponenten har ingen hardkodet fallback-tekst.
4.1.2 Navn, rolle, verdiAMerk triggerknappen som åpner dialogen. Sett aria-haspopup="dialog" på knappen som åpner modalen (og gjerne aria-controls som peker til dialogens id i ren HTML), så skjermlesere annonserer at knappen åpner en dialog.
3.3.4 Forhindring av feil (juridisk, økonomisk, data)AAVær varsom med lukking ved bakgrunnsklikk. Bakgrunnslukking er på som standard. Slå den av for skjemaer med ulagrede data og bekreftelse av destruktive handlinger: sett closeOnBackdropClick={false} (React) eller data-no-close-on-backdrop på <dialog> (HTML). Et utilsiktet klikk utenfor dialogen lukker den ellers umiddelbart og kan gi datatap.
2.4.3 FokusrekkefølgeAHold det viktigste innholdet synlig. Ved fullskjerm (size="full") eller mye innhold: sørg for at den sentrale informasjonen og handlingsknappene er synlige før brukeren må scrolle. Body-innholdet scroller, mens header og footer står fast.
Håndtert av komponenten (16)
KriteriumNivåHva komponenten gjør
1.3.1 Informasjon og relasjonerADialogen navngis programmessig via aria-labelledby fra <dialog> til Modal.Title. Struktur i header/body/footer følger DOM-rekkefølge, og role=dialog kommer fra det native <dialog>-elementet.
1.3.2 Meningsfull rekkefølgeAInnhold følger naturlig leserekkefølge i DOM: tittel og lukk-knapp (header) før innhold (body) før handlinger (footer).
1.4.3 Kontrast (minimum)AATekst bruker --ix-color-foreground-main-default på --ix-color-surface-main-default, som oppfyller kontrastkravet i både light og dark mode.
1.4.4 Endre tekststørrelseAARelative enheter (font-size-tokens) — tekst skalerer korrekt ved 200 % zoom uten at innhold brytes; body scroller ved behov.
1.4.10 OmflytAAMobile-first uten faste bredder — dialogen er nær full bredde på mobil og reflower korrekt ned til 320px viewport uten horisontal scroll.
1.4.11 Kontrast for ikke-tekstlig innholdAAFokusindikator på lukk-knapp og interaktive elementer oppfyller 3:1 kontrastkrav via --ix-outline-default. Den dempede ::backdrop er ikke informasjonsbærende.
1.4.13 Innhold ved hover eller fokusAADialogen viser ikke innhold ved hover/fokus som forsvinner; den er vedvarende til brukeren lukker den.
2.1.1 TastaturAFullt tastaturopererbart via native <dialog> åpnet med showModal(): alle handlinger er fokuserbare knapper som aktiveres med Enter/Space.
2.1.2 Ingen tastaturfelleAIngen tastaturfelle. Fokus-trap inne i dialogen er tilsiktet modal-oppførsel og har en escape-mekanisme: Escape lukker dialogen og frigjør fokus tilbake til triggeren.
2.4.3 FokusrekkefølgeAFokus flyttes til selve dialogen ved åpning (ikke til lukk-knappen) slik at skjermlesere annonserer «<tittel>, dialog»; Tab går deretter til første interaktive element. Konsumenten kan overstyre med autofocus på et element inni. Fokus føres tilbake til triggerknappen ved lukking. Tab-rekkefølgen følger DOM inne i dialogen.
2.4.7 Synlig fokusAATydelig fokusindikator på lukk-knapp og handlingsknapper (:focus-visible).
2.4.11 Fokus ikke skjult (minimum)AADialogen tegnes i nettleserens top-layer over alt annet innhold, så fokuserte elementer i dialogen blir ikke skjult av annet innhold.
2.5.8 Målstørrelse (minimum)AALukk-knappen har min. 44×44px touch-mål via min-width/min-height.
3.2.1 Ved fokusAIngen kontekstendring ved fokus. Å flytte fokus mellom elementer i dialogen laster ikke ny kontekst.
3.2.2 Ved inndataAIngen uventet kontekstendring ved input. Å aktivere en knapp gjør kun det forventede.
4.1.2 Navn, rolle, verdiANative <dialog> åpnet med showModal() gir role=dialog + aria-modal=true, tilgjengelig navn via aria-labelledby (Modal.Title), valgfri beskrivelse via aria-describedby (Modal.Description), og åpen/lukket-tilstand til skjermlesere uten ekstra ARIA. Lukk-knappen har tilgjengelig navn via påkrevd label; ikonet er dekorativt (aria-hidden).
Ikke relevant (34)
KriteriumNivåHvorfor ikke relevant
1.1.1 Ikke-tekstlig innholdAKomponenten har ikke eget bildeinnhold. Lukk-ikonet er dekorativt (aria-hidden); knappen navngis av label.
1.2.1 Bare lyd og bare video (forhåndsinnspilt)AIngen medieelementer.
1.2.2 Teksting (forhåndsinnspilt)AIngen medieelementer.
1.2.3 Synstolking eller mediealternativ (forhåndsinnspilt)AIngen medieelementer.
1.2.4 Teksting (direkte)AAIngen medieelementer.
1.2.5 Synstolking (forhåndsinnspilt)AAIngen medieelementer.
1.3.3 Sensoriske egenskaperAFormidler ikke instruksjoner basert på sensoriske egenskaper.
1.3.4 VisningsretningAADialogen låser ikke visningsretning (orientation).
1.3.5 Identifiser formål med inndataAADialogen er en beholder, ikke et inndatafelt. Eventuelle skjemafelt inni er konsumentens ansvar.
1.4.1 Bruk av fargeAFormidler ikke informasjon med farge alene.
1.4.2 Styring av lydAIngen lydelementer.
1.4.5 Bilder av tekstAAIngen bilder av tekst.
1.4.12 TekstavstandAA
2.1.4 TastatursnarveierAIngen tegnsnarveier.
2.2.1 Justerbar hastighetAIngen tidsbegrensning på dialogen.
2.2.2 Pause, stopp, skjulAInn-animasjonen er kort og engangs; ingen automatisk oppdaterende/blinkende innhold.
2.3.1 Terskelverdi på tre glimtAInn-animasjon (fade/scale) er kort, gir ingen blink, og slås av med prefers-reduced-motion.
2.4.1 Hoppe over blokkerABypass blocks gjelder gjentakende sideblokker, ikke en enkelt dialog.
2.4.2 SidetitlerADokumenttittel er sidens ansvar, ikke komponentens.
2.4.4 Formål med lenke (i kontekst)A
2.4.5 Flere måterAASidekrav — gjelder ikke enkeltkomponenter.
2.5.1 PekerbevegelserAIngen gestbaserte interaksjoner — enkelt klikk/tap.
2.5.2 Avbryt pekerANative knapper — nettleseren håndterer pekerinteraksjon (aktivering ved slipp).
2.5.4 BevegelsesaktiveringAIngen bevegelsesaktivering.
2.5.7 DrabevegelserAIngen dra-baserte interaksjoner.
3.1.1 Språk på sidenASidens språk er sidens ansvar.
3.2.3 Konsistent navigasjonAASidekrav — gjelder ikke enkeltkomponenter.
3.2.4 Konsistent identifikasjonAASystemkrav — gjelder konsistens på tvers av sider, ikke enkeltkomponenter.
3.2.6 Konsistent hjelpAIngen konsekvent hjelp-mekanisme i komponenten.
3.3.1 Identifikasjon av feilAKomponenten har ingen egen validering; feil i skjemafelt inni er konsumentens ansvar.
3.3.2 Ledetekster eller instruksjonerALedetekster for skjemafelt inni dialogen er konsumentens ansvar.
3.3.3 Forslag ved feilAA
3.3.7 Redundant oppføringAIngen gjentatt inntasting i selve komponenten.
3.3.8 Tilgjengelig autentisering (minimum)AAIngen autentisering i komponenten.

Props / API

PropTypeStandardBeskrivelse
openbooleanOm dialogen er åpen. Angi for kontrollert bruk — speiles til native showModal()/close(). Utelat for ukontrollert bruk
defaultOpenbooleanfalseStartverdi i ukontrollert modus (uten open). Ignoreres når open er satt
onOpenChange(open: boolean) => voidKalles når dialogen ber om å bli lukket (Escape, lukk-knapp eller bakgrunnsklikk). I kontrollert modus setter du open til false som respons; i ukontrollert modus er den valgfri (observatør)
size'small' | 'medium' | 'large' | 'full''medium'Bredde på større skjermer. Speiles til data-size (medium gir ingen attributt)
closeOnBackdropClickbooleantrueLukk ved klikk på bakgrunnen. Sett til false for skjemaer og destruktive handlinger
childrenReactNodeTypisk Modal.Header, Modal.Body og Modal.Footer
classNamestringEkstra CSS-klasser på <dialog>

Modal.Title

PropTypeStandardBeskrivelse
childrenReactNodeTittelteksten. Rendres som <h2> og kobles automatisk til dialogen via aria-labelledby
classNamestringEkstra CSS-klasser på <h2>

Modal.Description

Valgfri. Kobles automatisk til dialogen via aria-describedby, slik at skjermlesere leser beskrivelsen etter tittelen.

PropTypeStandardBeskrivelse
childrenReactNodeBeskrivelsesteksten. Rendres som <p> og kobles automatisk til dialogen via aria-describedby
classNamestringEkstra CSS-klasser på <p>

Modal.CloseButton

PropTypeStandardBeskrivelse
labelstringPåkrevd. Tilgjengelig navn (aria-label) på lukk-knappen. Skal oversettes til brukerens språk
...button-attributterAlle vanlige <button>-props

Modal.Header, Modal.Body, Modal.Footer, Modal.ButtonGroup

PropTypeStandardBeskrivelse
childrenReactNodeInnhold. Modal.ButtonGroup grupperer handlingsknapper i Modal.Footer
classNamestringEkstra CSS-klasser på wrapper-elementet (<div>)

Tilpasning med CSS

Trenger du ix-modal-stylingen på HTML du setter sammen selv, uten React-komponenten, kan du bruke klassene direkte på et native <dialog class="ix-modal">. Åpne det med dialog.showModal() for å få fokus-trap, Escape-lukking, dempet bakgrunn (::backdrop) og fokus-retur gratis.

Vil du i tillegg ha deklarativ åpning/lukking og scroll-lås uten egen JavaScript, laster du @sb1/indeks-web og bruker data-attributtene under.

Tilgjengelige klasser og selektorer

Element / tilstandSelektor / attributt
Dialog (rot).ix-modal<dialog>
Størrelse[data-size="small|medium|large|full"]<dialog>
Header (fast topp).ix-modal__header
Tittel.ix-modal__title (typisk <h2>)
Beskrivelse.ix-modal__description (typisk <p>)
Lukk-knapp.ix-modal__close
Innhold (scroller).ix-modal__body
Footer (fast bunn).ix-modal__footer
Knappegruppe.ix-modal__button-group
Bakgrunn.ix-modal::backdrop

Data-attributter for atferd (@sb1/indeks-web)

AttributtPlasseringEffekt
data-modal-open="<id>"Trigger-knappÅpner dialogen med matchende id (showModal())
data-modal-closeKnapp inne i dialogenLukker nærmeste <dialog>
data-no-close-on-backdrop<dialog>Slår av bakgrunnslukking (som ellers er på)

Eksempel: ren HTML

<button
class="ix-button"
data-variant="primary"
data-modal-open="min-dialog"
aria-haspopup="dialog"
aria-controls="min-dialog"
>
Åpne dialog
</button>

<dialog class="ix-modal" id="min-dialog" aria-labelledby="min-dialog-tittel">
<div class="ix-modal__header">
<h2 class="ix-modal__title" id="min-dialog-tittel">Tittel</h2>
<button class="ix-modal__close" aria-label="Lukk" data-modal-close>
<ix-icon name="close"></ix-icon>
</button>
</div>
<div class="ix-modal__body">
<p>Innhold i dialogen.</p>
</div>
<div class="ix-modal__footer">
<div class="ix-modal__button-group">
<button class="ix-button" data-variant="secondary" data-modal-close>Avbryt</button>
<button class="ix-button" data-variant="primary">Bekreft</button>
</div>
</div>
</dialog>