Gå til innholdet

Skjema med validering

Komponentene finnes hver for seg. Det som avgjør om et skjema er brukbart er avgjørelsene rundt dem: når du validerer, hvor fokus går, og hva feilmeldingen sier.

Prøv å sende skjemaet tomt, og deretter med en ufullstendig e-postadresse:

TidspunktHva som skjer
Mens brukeren skriverIngen nye feil. En feilmelding som alt står der, forsvinner i det feltet blir riktig.
Når feltet forlatesValider feltet, hvis brukeren har skrevet i det. Da er han ferdig med det, og meldingen avbryter ingen.
Når skjemaet sendesValider alt. Det er her alle feilene finnes samtidig.

Et felt brukeren ikke har rørt, sier vi ingenting om før innsending. «Skriv e-postadressen din» før du har begynt, er å kjefte på noen som ikke har gjort noe ennå.

Oppsummeringen bygges på nytt hver gang noe valideres, og leses ut av feltene. Retter du én av to feil, står den gjenværende igjen alene, og overskriften teller ned. En oppsummering som blir stående på det første tallet er verre enn ingen.

<label class="fs-label" data-required="symbol" for="epost">E-postadresse</label>
<input
class="fs-input"
id="epost"
type="email"
required
data-state="invalid"
aria-invalid="true"
aria-describedby="epost-hjelp epost-feil"
/>
<p class="fs-help-text" id="epost-hjelp">Vi sender kvittering hit.</p>
<p class="fs-error-text" id="epost-feil">Skriv en e-postadresse med krøllalfa.</p>

data-state gir fargen, aria-invalid sier fra til skjermleseren, og aria-describedby peker på teksten. Mangler den siste, finnes feilmeldingen bare for den som ser den.

Feilmeldingen skal ut av aria-describedby igjen når feltet blir gyldig. Ellers peker feltet på en skjult tekst, og skjermlesere melder enten ingenting eller noe som ikke står på skjermen.

Slipper du å holde styr på dette selv, bruk Field eller fs.field(). De regner ut alle tre fra én tilstand.

Error Summary står øverst i skjemaet og samler feilene. Du skriver boksen, overskriften og lista med fs.errorSummary(). Komponenten gjør de to tingene du ellers måtte huske: flytter fokus til seg selv når den kommer til syne, og sender fokus til riktig felt når en lenke følges.

<fs-error-summary class="fs-error-summary" role="alert" tabindex="-1" id="skjemafeil">
<h2 class="fs-error-summary__title">Skjemaet har to feil</h2>
<ul class="fs-list">
<li><a href="#epost">Skriv en e-postadresse med krøllalfa</a></li>
<li><a href="#fodselsdato">Skriv en dato som finnes</a></li>
</ul>
</fs-error-summary>

Overskriften og hidden skrives av den som rendrer skjemaet, ikke av komponenten. Er lista tom, setter fs.errorSummary({ count: 0 }) hidden på boksen, og overskriften blir stående urørt: «Skjemaet har 0 feil» er det siste brukeren skal få lest opp.

Lenketeksten er feilmeldingen, ikke navnet på feltet. Den som hører lista skal forstå hva som er galt uten å hoppe til feltet først.

Bygg lista på nytt hver gang du validerer, også når de samme feilene står igjen. Ved innsending leses den da opp på nytt, og brukeren vet at forsøket ble avvist.

type="email" godtar ola@eksempel, uten punktum og toppdomene. Det er en gyldig adresse på et internt nett, og derfor riktig av nettleseren, men i et skjema som sender kvittering på e-post er det nesten alltid en skrivefeil. Demoen legger derfor på én regel til:

const ADRESSE = /^[^\s@]+@[^\s@.]+(\.[^\s@.]+)+$/
const gyldig = felt.checkValidity() && ADRESSE.test(felt.value)

Strengere enn dette er ikke verdt det. En adresse er først bekreftet når noen har klikket på lenken i e-posten du sendte.

Nettleserens validering fanger format. Serveren fanger alt det andre: at adressen ikke finnes i registeret, at fristen er gått ut, at noen andre har søkt på det samme. De to må ende opp på samme sted.

const skjema = document.getElementById("soknad")
skjema.addEventListener("submit", async (hendelse) => {
hendelse.preventDefault()
const svar = await fetch("/api/soknad", {
method: "POST",
body: new FormData(skjema),
})
if (svar.ok) return
const { feil } = await svar.json() // [{ felt: "epost", melding: "…" }]
for (const { felt: navn, melding } of feil) {
const felt = skjema.elements[navn]
felt.setAttribute("aria-invalid", "true")
felt.dataset.state = "invalid"
document.getElementById(`${felt.id}-feil`).textContent = melding
}
// Den samme oppsummeringen som over, bygget av feilene serveren sendte.
document.querySelector("#skjemafeil ul").innerHTML = feil
.map(({ felt, melding }) => `<li><a href="#${felt}">${melding}</a></li>`)
.join("")
})

Har feilen ikke noe felt, for eksempel «tjenesten er nede», hører den i en Alert over skjemaet, ikke i oppsummeringen. Oppsummeringen er en liste over ting brukeren kan rette.

  • Si hva brukeren skal gjøre: «Skriv en dato som finnes», ikke «Ugyldig verdi».
  • Ta med et eksempel når formatet er uvanlig: «for eksempel ola@eksempel.no».
  • Skriv på norsk. Nettleserens egne meldinger følger nettleserspråket, ikke sidens, så validationMessage kan dukke opp på engelsk midt i et norsk skjema. Demoen over bruker derfor egne tekster.
  • Dropp utropstegnet, og ikke legg skylden på brukeren.
  • Rekkefølgen på feltene. Tastaturet følger markupen.
  • Hva som er påkrevd. data-required er bare visuelt, og required på feltet er det som stopper innsendingen.
  • Lagring av utkast. Et langt skjema bør tåle at nettet forsvinner, men det er appens ansvar.