Tabs
Serveren skriver rollene og skjuler panelene som ikke er valgt. Komponenten tar piltastene.
Det er hele grunnen til at den finnes. I en fanerad skal Tab hoppe forbi hele raden og inn i panelet, mens piltastene flytter mellom fanene. Et sett knapper uten den koblingen gir én tabbestopp per fane, og skjermleseren sier «knapp» der den skulle sagt «fane, 2 av 3, valgt».
// main.ts, kjøres én gang når appen starterimport { defineFsTabs } from "@fristil/designsystem/tabs"import "@fristil/designsystem/tabs.css"
defineFsTabs()Søknaden ble sendt 4. mars 2026. Saksnummer 2026-0481.
Tre vedlegg er lastet opp. Siste kom 6. mars.
Ingen meldinger i denne saken ennå.
Markupen
Section titled “Markupen”fs.tabs() gir ett attributtsett per element. Id-ene og koblingen mellom fane og panel regnes ut av byggefunksjonen, så de kan ikke komme i utakt.
const faner = fs.tabs({ id: "sak", count: 3, selected: 0, label: "Deler av saken" })<fs-tabs> <div class="fs-tabs__list" role="tablist" aria-label="Deler av saken"> <button id="sak-tab-0" role="tab" type="button" aria-selected="true" aria-controls="sak-panel-0" tabindex="0">Søknaden</button> <button id="sak-tab-1" role="tab" type="button" aria-selected="false" aria-controls="sak-panel-1" tabindex="-1">Vedlegg</button> </div>
<div id="sak-panel-0" class="fs-tabs__panel" role="tabpanel" aria-labelledby="sak-tab-0" tabindex="0"> <p class="fs-paragraph">Søknaden ble sendt 4. mars 2026.</p> </div> <div id="sak-panel-1" class="fs-tabs__panel" role="tabpanel" aria-labelledby="sak-tab-1" tabindex="0" hidden> <p class="fs-paragraph">Tre vedlegg er lastet opp.</p> </div></fs-tabs>hidden på panelene kommer fra serveren, ikke fra komponenten. Gjorde komponenten det, ville alle panelene vises til skriptet hadde kjørt, og innholdet hoppe når de skjulte seg selv.
Sender serveren området på nytt mens brukeren holder på, river oppdateringen fanevalget bort, siden ingenting av det sto i HTML-en den sendte. Komponenten ser det og setter valget tilbake, så malen din trenger ingenting ekstra. Skal serveren kunne flytte fanen selv, som i «gå videre til steg 2», settes server-controlled på <fs-tabs>. Se Markup og oppførsel.
Hvilken fane som er valgt
Section titled “Hvilken fane som er valgt”Komponenten har ikke noe selected-attributt. Svaret står i markupen, som aria-selected på fanen, og leses derfra. To steder å holde i synk er ett for mye.
const faner = document.querySelector("fs-tabs")
faner.selected // 0faner.select(1) // velger «Vedlegg» og sender `tab-select`I en app
Section titled “I en app”<fs-tabs id="saksfaner"> <div class="fs-tabs__list" role="tablist" aria-label="Deler av saken"> <button id="sak-tab-0" role="tab" type="button" aria-selected="true" aria-controls="sak-panel-0" tabindex="0">Søknaden</button> <button id="sak-tab-1" role="tab" type="button" aria-selected="false" aria-controls="sak-panel-1" tabindex="-1">Vedlegg</button> </div> <div id="sak-panel-0" class="fs-tabs__panel" role="tabpanel" aria-labelledby="sak-tab-0" tabindex="0">Søknaden</div> <div id="sak-panel-1" class="fs-tabs__panel" role="tabpanel" aria-labelledby="sak-tab-1" tabindex="0" hidden>Vedlegg</div></fs-tabs>
<script type="module"> import { defineFsTabs } from "@fristil/designsystem/tabs" defineFsTabs()
document.getElementById("saksfaner").addEventListener("tab-select", (evt) => { console.log("valgte fane", evt.detail.index) })</script>import { fs } from "@fristil/designsystem/react"import { defineFsTabs } from "@fristil/designsystem/tabs"
defineFsTabs()
const TITLER = ["Søknaden", "Vedlegg", "Meldinger"]
export function Saksfaner() { const faner = fs.tabs({ id: "sak", count: TITLER.length, label: "Deler av saken" })
return ( <fs-tabs> <div {...faner.list}> {faner.tabs.map((fane, i) => ( <button key={fane.id} {...fane}>{TITLER[i]}</button> ))} </div> {faner.panels.map((panel, i) => ( <div key={panel.id} {...panel}>Innhold for {TITLER[i]}</div> ))} </fs-tabs> )}Importen fra @fristil/designsystem/react gir className og tabIndex i stedet for HTML-navnene, så React ikke advarer.
---import { fs } from "@fristil/designsystem"
const titler = ["Søknaden", "Vedlegg", "Meldinger"]const faner = fs.tabs({ id: "sak", count: titler.length, label: "Deler av saken" })---
<fs-tabs> <div {...faner.list}> {faner.tabs.map((fane, i) => <button {...fane}>{titler[i]}</button>)} </div> {faner.panels.map((panel, i) => <div {...panel}>Innhold for {titler[i]}</div>)}</fs-tabs>
<script> import { defineFsTabs } from "@fristil/designsystem/tabs" defineFsTabs()</script><fs-tabs server-controlled data-on:tab-select="$valgtFane = evt.detail.index"> <div class="fs-tabs__list" role="tablist" aria-label="Deler av saken"> <button id="sak-tab-0" role="tab" type="button" aria-selected="true" aria-controls="sak-panel-0" tabindex="0">Søknaden</button> </div> <div id="sak-panel-0" class="fs-tabs__panel" role="tabpanel" aria-labelledby="sak-tab-0" tabindex="0">Søknaden</div></fs-tabs>server-controlled hører med når et signal styrer hvilken fane som vises. Uten det kjemper de to om det samme: komponenten setter tilbake fanen brukeren valgte, mens serveren sender sin egen. Lar du signalet være, trengs ingenting, og komponenten holder valget selv.
Tilgjengelighet
Section titled “Tilgjengelighet”Fanene er role="tab" i en role="tablist", og panelene er role="tabpanel" pekt til med aria-labelledby. Alt dette står i markupen fra serveren, så semantikken er riktig før skriptet har kjørt.
| Tast | Gjør |
|---|---|
| Piltastene | Flytter mellom fanene, og går rundt fra siste til første |
| Home og End | Første og siste fane |
| Tab | Ut av raden og inn i det valgte panelet |
Bare den valgte fanen har tabindex="0". Panelet har tabindex="0" slik at det får fokus også når det ikke har noe fokuserbart i seg, ellers hopper Tab rett forbi innholdet som nettopp ble vist.
Klasser du kan style
Section titled “Klasser du kan style”| Klasse | Hva det er |
|---|---|
.fs-tabs__list | Raden med knapper. Serveren gir den role="tablist" |
.fs-tabs__panel | Ett panel. Den valgte er uten hidden |
Knappene har ingen egen klasse, de styles som .fs-tabs__list button.
| Variabel | Reserve |
|---|---|
--fs-tabs-gap | var(--size-1) |
--fs-tabs-padding | var(--size-2) var(--size-4) |
--fs-tabs-panel-space | var(--size-4) |
.fs-tabs__list { --fs-tabs-gap: 0;}