PDFAccess PDF til tilgængeligt webindhold
← Blog
Tilgængelighed

Tab-tasten i dybden: Fokusrækkefølge, tabindex og aktive tilstande

Tab-tasten er grundstenen i tastaturnavigation — men korrekt implementering kræver langt mere end at lade browseren styre. Denne artikel gennemgår den naturlige fokusrækkefølge, de tre varianter af tabindex, hvad der sker når fokus rammer en knap, et link eller et faneblad, og hvad WCAG kræver af den visuelle fokus-indikator — med konkrete kodeeksempler.

Tab-tastens funktion i browseren

Tab-tasten flytter tastatur-fokus fremad gennem en sekvens af fokuserbare elementer på siden. Shift+Tab bevæger sig i den omvendte retning. For mange brugere er dette den eneste navigationsmetode: personer med motoriske funktionsnedsættelser, blinde brugere der navigerer med skærmlæser, og brugere med midlertidige skader anvender Tab som primært input.

Browseren vedligeholder internt et fokusindeks og kender den aktuelle position. Hvert tryk på Tab overfører fokus til det næste fokuserbare element — forudsat at elementet er synligt, ikke er disabled, og har en tabindex-værdi på 0 eller derover.

Disse HTML-elementer er nativt fokuserbare og indgår i tab-sekvensen uden yderligere attributter:

  • <a> med href-attribut
  • <button> (medmindre den er disabled)
  • <input>, <select>, <textarea> (medmindre de er disabled)
  • <details> og <summary>

Elementer som <div>, <span> og <p> er ikke naturligt fokuserbare. De kræver en eksplicit tabindex-attribut for at indgå i tab-sekvensen — og det er netop her, mange implementeringer introducerer fejl.

Den naturlige tab-rækkefølge og DOM-orden

Uden eksplicitte tabindex-værdier følger tab-rækkefølgen DOM-orden — den rækkefølge, elementerne optræder i HTML-koden. For de fleste sider svarer DOM-orden til den visuelle rækkefølge: øverst til venstre, nedad og til højre. Det er også præcis, hvad WCAG 2.4.3 (Fokusrækkefølge, Niveau A) kræver: fokus skal bevæge sig i en rækkefølge, der er logisk og meningsfuld.

Problemet opstår, når CSS bruges til at ændre den visuelle præsentation uden at ændre DOM-orden. Et klassisk eksempel er flex-layouts, hvor elementerne er omplaceret visuelt, men DOM-orden er uændret:

<!-- DOM-orden: "Knap 3" → "Knap 2" → "Knap 1" -->
<!-- Visuelt vises de i omvendt rækkefølge pga. flex-direction: row-reverse -->
<div style="display: flex; flex-direction: row-reverse;">
  <button>Knap 3</button>
  <button>Knap 2</button>
  <button>Knap 1</button>
</div>

Her vil Tab-tasten bevæge sig fra “Knap 3” til “Knap 2” til “Knap 1” — modsat den visuelle rækkefølge. Det er et brud på WCAG 2.4.3. Løsningen er altid at tilpasse DOM-orden til den visuelle logik — ikke at forsøge at rette det med tabindex.

tabindex — tre værdier, tre formål

tabindex-attributten styrer, om og hvornår et element indgår i tab-sekvensen. Der er tre distinkte værdier med meget forskellig adfærd, og det er vigtigt at forstå præcis hvad hver af dem gør.

tabindex=“-1”

Et element med tabindex="-1" kan modtage fokus programmatisk via JavaScript (.focus()), men indgår ikke i den naturlige tab-sekvens — brugeren kan ikke tabbe til det med Tab-tasten. Det er det korrekte valg i situationer, hvor et element skal kunne fokuseres under visse betingelser, men ikke altid:

// Åbn modal og sæt fokus på første element inde i den
function openModal() {
  const modal = document.getElementById('dialog');
  modal.removeAttribute('hidden');

  const firstFocusable = modal.querySelector(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  if (firstFocusable) firstFocusable.focus();
}

tabindex="-1" bruges typisk til: modaler og dialoger, skip-links der aktiveres programmatisk, og elementer i composite-widgets (som faneblade) der styres med piletaster i stedet for Tab.

tabindex=“0”

tabindex="0" tilføjer et element til den naturlige tab-sekvens på dets placering i DOM-orden. Det bruges primært til brugerdefinerede interaktive elementer baseret på semantisk neutrale tags som <div> eller <span>:

<!-- Brugerdefineret knap-lignende element -->
<div
  role="button"
  tabindex="0"
  onclick="handleClick()"
  onkeydown="handleKeyDown(event)"
>
  Åbn menu
</div>
function handleKeyDown(event) {
  // Knapper aktiveres med Enter OG mellemrumstasten
  if (event.key === 'Enter' || event.key === ' ') {
    event.preventDefault();
    handleClick();
  }
}

Vigtig bemærkning: tabindex="0" løser udelukkende fokusering. Det tilføjer ikke tastaturhåndtering, semantik eller tilgængelighed i øvrigt. Brug native HTML-elementer (<button>, <a>) når det overhovedet er muligt — de har tastaturhåndtering og semantisk rolle indbygget.

tabindex med positiv værdi

Positive tabindex-værdier (tabindex="1", tabindex="2" osv.) placerer et element foran alle andre i tab-rækkefølgen, uanset DOM-position. I praksis er det næsten altid en fejl:

<!-- Undgå dette konsekvent -->
<input tabindex="3" type="text" placeholder="Felt A">
<input tabindex="1" type="text" placeholder="Felt B"> <!-- Nås første! -->
<input tabindex="2" type="text" placeholder="Felt C">

Positive tabindex-værdier skaber uforudsigelig navigation, er ekstremt vanskelige at vedligeholde i dynamiske applikationer, og er en hyppig årsag til de “logisk forkerte” fokusrækkefølger, Digitaliseringsstyrelsen opdager i tilsynsrapporter. Løsningen er konsekvent at ændre DOM-orden i stedet.

Knapper og links er de hyppigste interaktive elementer — og der er præcise, distinkte regler for, hvordan Tab opfører sig i relation til dem begge.

Knapper

En <button> er nativt fokuserbar og indgår i tab-sekvensen uden yderligere attributter. Når Tab sætter fokus på en knap, modtager den fokus og kan aktiveres med Enter eller mellemrumstasten. Tab-tasten bevæger sig videre til næste element i sekvensen.

En knap med disabled-attributten fjernes fuldstændigt fra tab-sekvensen og annonceres ikke af skærmlæsere. Hvis en knap er midlertidigt utilgængelig, men brugeren bør vide om dens eksistens og årsagen til, at den er deaktiveret, er aria-disabled="true" den korrekte tilgang:

<!-- Knap der forbliver i tab-sekvensen og annonceres som deaktiveret -->
<button
  type="button"
  aria-disabled="true"
  onclick="preventIfDisabled(event)"
>
  Send formular (udfyld alle felter først)
</button>
function preventIfDisabled(event) {
  if (event.currentTarget.getAttribute('aria-disabled') === 'true') {
    event.preventDefault();
  }
}

Et <a>-element er kun fokuserbart og tabbart, hvis det har en href-attribut. <a> uden href er et anker — ikke et link — og indgår ikke i tab-sekvensen og annonceres ikke som interaktivt:

<a href="/om-os">Om os</a>          <!-- Fokuserbar — korrekt ✓      -->
<a>Overskrift-anker</a>             <!-- Ikke fokuserbar ✗            -->
<a href="#">Tom destination</a>     <!-- Fokuserbar, men undgå ✗     -->

Links aktiveres med Enter — ikke med mellemrumstasten. Det er en vigtig distinktion fra knapper, som aktiveres med begge taster. Skærmlæserbrugere lærer disse konventioner at kende og forventer dem overholdt.

Faneblade og roving tabindex

Tab-komponenter — faneblade — er et særtilfælde med egne navigationsregler baseret på WAI-ARIA Authoring Practices. Det er her, mange implementeringer fejler alvorligt og introducerer uforudsigelig adfærd for tastaturbrugere.

Et korrekt tab-panel-mønster bruger ét enkelt tabstop for hele tab-listen og piletasterne til navigation inden for listen. Dette kaldes “roving tabindex” — mønsteret, hvor kun ét element i en gruppe har tabindex="0" ad gangen, mens de øvrige sættes til tabindex="-1":

<div role="tablist" aria-label="Dokumentkategorier">
  <button
    role="tab"
    id="tab-1"
    aria-selected="true"
    aria-controls="panel-1"
    tabindex="0"
  >
    Vejledninger
  </button>
  <button
    role="tab"
    id="tab-2"
    aria-selected="false"
    aria-controls="panel-2"
    tabindex="-1"
  >
    Skabeloner
  </button>
  <button
    role="tab"
    id="tab-3"
    aria-selected="false"
    aria-controls="panel-3"
    tabindex="-1"
  >
    Rapporter
  </button>
</div>

<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
  Indhold for Vejledninger
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
  Indhold for Skabeloner
</div>
<div role="tabpanel" id="panel-3" aria-labelledby="tab-3" hidden>
  Indhold for Rapporter
</div>

JavaScript-logikken håndterer piletasterne og opdaterer tabindex og aria-selected dynamisk:

const tabs = document.querySelectorAll('[role="tab"]');
const panels = document.querySelectorAll('[role="tabpanel"]');

tabs.forEach((tab, index) => {
  tab.addEventListener('keydown', (e) => {
    let targetIndex;

    if (e.key === 'ArrowRight') {
      targetIndex = (index + 1) % tabs.length;
    } else if (e.key === 'ArrowLeft') {
      targetIndex = (index - 1 + tabs.length) % tabs.length;
    } else if (e.key === 'Home') {
      targetIndex = 0;
    } else if (e.key === 'End') {
      targetIndex = tabs.length - 1;
    } else {
      return; // Alle andre taster håndteres normalt
    }

    e.preventDefault();
    activateTab(targetIndex);
  });
});

function activateTab(targetIndex) {
  // Nulstil alle tabs
  tabs.forEach((t) => {
    t.setAttribute('tabindex', '-1');
    t.setAttribute('aria-selected', 'false');
  });
  panels.forEach((p) => (p.hidden = true));

  // Aktivér den valgte tab
  tabs[targetIndex].setAttribute('tabindex', '0');
  tabs[targetIndex].setAttribute('aria-selected', 'true');
  tabs[targetIndex].focus();
  panels[targetIndex].hidden = false;
}

Denne implementering sikrer:

  • Ét enkelt tabstop for hele tab-listen — Tab-tasten passerer hele listen som én enhed og går videre til næste interaktive element på siden
  • Piletasterne navigerer mellem fanebladene inden for listen
  • Home og End springer til første og sidste tab
  • aria-selected opdateres korrekt, så skærmlæsere annoncerer den aktive tab

Fokus-indikatoren: WCAG-krav og implementering

Fokus-indikatoren er det visuelle signal, der viser tastaturbrugeren, hvilket element der aktuelt har fokus. Det er tastaturbrugerens ækvivalent til musemarkøren — uden den er meningsfuld navigation umulig.

WCAG 2.4.7 (Synligt fokus, Niveau AA) kræver, at fokusindikatoren er synlig. WCAG 2.2 tilføjede det mere præcise succeskriterie 2.4.11 (Fokus-fremtoning, Niveau AA), der stiller minimumskrav til størrelse og kontrast: fokusindikatoren skal have et minimumsareal svarende til en 2 px bred kant rundt om komponenten, og kontrasten mod de tilstødende farver skal være mindst 3:1.

Den hyppigste fejl er at fjerne browserens standardindikator uden et alternativ — typisk via:

/* Fejl — fjerner fokusindikator helt */
* {
  outline: none;
}

button:focus {
  outline: 0;
}

En korrekt og robust implementering bruger :focus-visible, en moderne CSS-pseudoklasse der aktiverer fokus-stilen kun ved tastaturnavigation — ikke ved museklik. Det løser det designmæssige problem med synlige fokusringe ved museklik, uden at fjerne dem for tastaturbrugere:

/* Globalt: bevar browser-outline som fallback */
:focus {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

/* Avanceret: differentier tastatur og mus */
:focus:not(:focus-visible) {
  outline: none; /* Ingen ring ved museklik */
}

:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 3px;
  border-radius: 2px;
}

Browser-support for :focus-visible er bred (Chrome 86+, Firefox 85+, Safari 15.4+) og understøttes i alle aktuelle browsers.

Aktiv tilstand når Tab rammer en knap

Når Tab-tasten sætter fokus på en knap, befinder knappen sig i :focus-visible-tilstanden. Det er ikke det samme som :active (knappen trykkes fysisk ned) eller :hover (musmarkøren er over knappen). Disse CSS-tilstande er distinkte og bør styles uafhængigt:

button {
  background-color: #1a56db;
  color: #ffffff;
  border: 2px solid transparent;
  padding: 0.5rem 1.25rem;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

/* Mus-hover */
button:hover {
  background-color: #1e429f;
}

/* Tastatur-fokus — synlig og kontrasttjekket */
button:focus-visible {
  outline: 3px solid #fbbf24;  /* Gul — høj kontrast mod blå baggrund */
  outline-offset: 3px;
  background-color: #1e429f;
}

/* Aktiv (knappen trykkes ned) */
button:active {
  background-color: #1e3a8a;
  transform: translateY(1px);
}

Fokus-outline-farven #fbbf24 (gul) mod knappens blå baggrund #1e429f giver en kontrastforhold på ca. 7:1 — langt over minimumskravet på 3:1 i WCAG 2.4.11.

Toggle-knapper og aria-pressed

For knapper der skifter tilstand — aktivér/deaktivér, til/fra — brug aria-pressed-attributten. Skærmlæsere annoncerer aria-pressed="true" som “trykket” og aria-pressed="false" som “ikke trykket”, hvilket giver brugeren information om knappens aktuelle tilstand:

<button type="button" id="toggle-notifications" aria-pressed="false">
  Notifikationer
</button>
document.getElementById('toggle-notifications').addEventListener('click', function () {
  const isPressed = this.getAttribute('aria-pressed') === 'true';
  this.setAttribute('aria-pressed', String(!isPressed));
  // Håndtér forretningslogik her
});
/* Visuel tilstand når knappen er aktiveret */
[aria-pressed="true"] {
  background-color: #1e3a8a;
  border-color: #93c5fd;
}

[aria-pressed="true"]:focus-visible {
  outline-color: #fbbf24;
}

Bemærk at aria-pressed kommunikerer tilstand — ikke det samme som aria-selected, der bruges i tab-paneler og listebokse. Brug det rigtige attribut til den rette kontekst.

Fokusstyring i modaler og dialoger

En modal dialog er et af de mest komplekse scenarier for tab-fokus. Når en modal åbnes, skal tre ting ske korrekt:

  1. Fokus flyttes ind i modalen til det første interaktive element
  2. Mens modalen er åben, holdes fokus inde (intentionel fokus-fælde — en tilladt undtagelse fra WCAG 2.1.2)
  3. Når modalen lukkes, returneres fokus til det element, der åbnede den
let lastFocusedElement = null;

function openModal(modalId) {
  lastFocusedElement = document.activeElement;

  const modal = document.getElementById(modalId);
  modal.removeAttribute('hidden');
  modal.setAttribute('aria-modal', 'true');

  // Sæt fokus på første interaktive element i modalen
  const focusableSelectors =
    'button:not([disabled]), [href], input:not([disabled]), ' +
    'select:not([disabled]), textarea:not([disabled]), ' +
    '[tabindex]:not([tabindex="-1"])';

  const firstFocusable = modal.querySelector(focusableSelectors);
  if (firstFocusable) firstFocusable.focus();

  // Aktivér fokus-fælde
  modal.addEventListener('keydown', handleModalKeyDown);
}

function handleModalKeyDown(e) {
  const modal = e.currentTarget;

  // Escape lukker modalen
  if (e.key === 'Escape') {
    closeModal(modal.id);
    return;
  }

  if (e.key !== 'Tab') return;

  const focusableSelectors =
    'button:not([disabled]), [href], input:not([disabled]), ' +
    'select:not([disabled]), textarea:not([disabled]), ' +
    '[tabindex]:not([tabindex="-1"])';

  const focusable = Array.from(modal.querySelectorAll(focusableSelectors));
  const first = focusable[0];
  const last = focusable[focusable.length - 1];

  if (e.shiftKey) {
    // Shift+Tab fra første element → hop til sidste
    if (document.activeElement === first) {
      e.preventDefault();
      last.focus();
    }
  } else {
    // Tab fra sidste element → hop til første
    if (document.activeElement === last) {
      e.preventDefault();
      first.focus();
    }
  }
}

function closeModal(modalId) {
  const modal = document.getElementById(modalId);
  modal.setAttribute('hidden', '');
  modal.removeEventListener('keydown', handleModalKeyDown);

  // Returner fokus til elementet, der åbnede modalen
  if (lastFocusedElement) lastFocusedElement.focus();
}

Fokus-fælden i modaler er en intentionel og specifikationskonform undtagelse fra WCAG 2.1.2. WAI-ARIA-specifikationen tillader og kræver fokus-fælder i modale dialoger, fordi aria-modal="true" signalerer til skærmlæseren, at resten af siden er blokeret — og brugeren forventer at fokus forbliver inde.

Sammenhæng med PDF-dokumenter

PDF-dokumenter stiller særlige udfordringer for tab-navigation. Et PDF/UA-kompatibelt dokument skal kunne navigeres med tastatur i Adobe Acrobat Reader — men adfærden er markant anderledes og langt mindre forudsigelig end i HTML.

Tab-rækkefølgen i PDF-dokumenter afhænger af dokumentets interne tag-struktur og er ikke altid tilgængelig for tesning via de metoder, der bruges til HTML. Skærmlæserbrugere oplever ofte, at tab-fokus hopper til uventede steder, eller at interaktive elementer som formularfelter og links ikke nås i en logisk rækkefølge.

HTML-formatet giver en langt mere forudsigelig og testbar tab-navigation. Konvertering af eksisterende PDF-dokumenter til semantisk HTML — f.eks. via PDFAccess direkte i browseren — giver et format, der understøtter de tab-mønstre, der er beskrevet i denne artikel, og som kan testes og valideres med standard browserværktøjer.

Tjekliste: Tab-navigation

Brug denne tjekliste til teknisk gennemgang af tab-implementeringen på dit websted:

Fokusrækkefølge og tabindex

  • DOM-orden svarer til den visuelle rækkefølge (WCAG 2.4.3)
  • Ingen positive tabindex-værdier — brug DOM-orden i stedet
  • Brugerdefinerede interaktive elementer har tabindex="0" og korrekt role
  • Elementer der styres med piletaster (som tab-lister) bruger roving tabindex

Fokus-indikator

  • Ingen outline: none uden visuelt alternativ (WCAG 2.4.7)
  • Fokusindikatoren har kontrast på mindst 3:1 mod tilstødende farver (WCAG 2.4.11)
  • :focus-visible bruges til at differentiere tastatur-fokus og museklik
  • Knapper aktiveres med Enter og mellemrumstasten
  • Links aktiveres med Enter
  • aria-disabled bruges frem for disabled, når knappen skal forblive i tab-sekvensen
  • Toggle-knapper bruger aria-pressed til at kommunikere tilstand

Faneblade (tabs)

  • Tab-listen har ét enkelt tabstop (roving tabindex)
  • Piletasterne navigerer mellem faneblade inden for listen
  • Home og End understøttes
  • aria-selected opdateres korrekt ved skift

Modaler og dialoger

  • Fokus flyttes ind i modalen ved åbning
  • Fokus holdes inde i modalen (intentionel fokus-fælde)
  • Escape-tasten lukker modalen
  • Fokus returneres til åbningsknappen ved lukning

For et bredere overblik over WCAG 2.1-kravene til tastaturnavigation — herunder succeskriterier 2.1.1, 2.1.2 og 2.4.7 — se Tastaturnavigation og WCAG 2.1: Hvad kræves, og hvordan tester du det?.