Gå til innholdet

Field

Et skjemafelt er tilgjengelig når hjelpeteksten og feilmeldingen er koblet til selve feltet. Fristil gir deg den koblingen på to måter, og du skal bruke den ene, ikke begge.

Lager du markupen med JavaScript?Bruk
Ja. React, Astro, en Node-server, eller en app helt uten serverfs.field(). Da trenger du ikke web componenten
Nei. En Go-mal, en PHP-fil, en Razor-visning eller håndskrevet HTML<fs-field>

Begge bruker den samme funksjonen under, så kontrakten kan ikke gå fra hverandre. Forskjellen er bare om koden som lager HTML-en kan kalle en TypeScript-funksjon.

<fs-field> lager ingen markup selv og bruker ikke Shadow DOM, så FormData, skjemabiblioteker og testverktøy finner elementene som før.

Et felt med hjelpetekst og feilmelding krever fem koblinger som må stemme samtidig:

<label for="epost">E-postadresse</label>
<input id="epost" aria-invalid="true" aria-describedby="epost-hjelp epost-feil" />
<p id="epost-hjelp">Vi sender kvittering hit.</p>
<p id="epost-feil">Skriv en gyldig e-postadresse.</p>

Hver id må være unik, aria-describedby må peke på begge tekstene, og aria-invalid må følge valideringen. Fem steder å gjøre feil per felt, ganget med antall felt. Og det synes ikke på skjermen når en av dem mangler.

fs.field regner ut attributtene der du rendrer, enten ved bygging i Astro, på hver render i React, eller på serveren. Nettleseren trenger ingen registrert komponent, og koblingen står ferdig i HTML-en før noe skript har kjørt:

const felt = fs.field({ id: "epost", required: "symbol", help: true, error: true, invalid })
<label {...felt.label}>E-postadresse</label>
<input {...fs.input({ type: "email" })} {...felt.control} />
<p {...fs.helpText()} {...felt.help}>Vi sender kvittering hit.</p>
<p {...fs.errorText()} {...felt.error}>Skriv en gyldig adresse.</p>

Bruker du React, importer fra @fristil/designsystem/react, som gir className og htmlFor i stedet for HTML-navnene.

id er påkrevd. I React kommer den fra useId(). Ellers er feltets eget navn som regel det opplagte valget. Vet du sikkert at markupen rendres én gang, som på en Astro-side, kan du kalle createFieldId(), som du får fra både @fristil/designsystem og @fristil/designsystem/react.

Grunnen er at en id som lages av seg selv er tilfeldig: to kjøringer gir to ulike. Rendres det samme feltet på en server og så i nettleseren, peker for og aria-describedby på noe annet enn det som står der, og React melder avvik ved hydreringen.

Skriver du JavaScript uten typer, ser du ingen type. Da lager funksjonen en id likevel, og sier fra i konsollen, så feltet virker mens du retter det.

Kan ikke koden som lager HTML-en kalle en TypeScript-funksjon, gjør <fs-field> den samme koblingen i nettleseren. Du skriver elementene dine som vanlig, og komponenten setter for, id, aria-describedby og aria-invalid på dem.

Sender serveren det samme området på nytt mens brukeren holder på, som i en Datastar-app, river morfingen koblingen bort: ingenting av den sto i HTML-en serveren sendte. Komponenten ser det og setter den tilbake, med de samme id-ene som før, så malen din trenger ingen data-preserve-attr. Begrunnelsen står i Markup og oppførsel.

Finner komponenten ikke delene sine, sier den fra i konsollen. Et <fs-field> uten en kontroll gir en advarsel med navnet på det som mangler, framfor et felt som stilltiende står uten kobling. Det samme gjelder et felt uten ledetekst, men bare når det heller ikke har et navn på annet vis: en <label for> utenfor elementet, aria-label eller aria-labelledby på kontrollen teller, som i et søkefelt med bare et ikon.

// main.ts, kjøres én gang når appen starter
import { defineFsField } from "@fristil/designsystem/field"
import "@fristil/designsystem/field.css"
defineFsField()

defineFsField() registrerer web componenten i nettleseren. Kall den én gang ved oppstart. Flere kall er ufarlige.

Stilarket må importeres fordi barna er dine egne elementer med klassene fs-label, fs-input og så videre, og de klassene må ligge på siden. field.css samler dem.

<label> har ikke for, og tekstene har ikke id. Komponenten fyller ut begge deler.

<fs-field>
<label>Fullt navn</label>
<input class="fs-input" type="text" />
</fs-field>

Klikk på teksten flytter fokus til feltet, og skjermleseren leser riktig ledetekst.

Hver gang innholdet eller attributtene endrer seg, finner den <label> og første kontroll blant barna og:

  1. Gir kontrollen en id hvis den mangler, og setter label[for] til den.
  2. Gir hjelpetekst og feilmelding id ved behov.
  3. Setter aria-describedby på kontrollen, med begge tekstene når begge finnes.
  4. Setter aria-invalid="true" på kontrollen når invalid er satt.
  5. Setter data-required eller data-optional på ledeteksten ut fra attributtene på <fs-field>.
  6. Viser eller skjuler fs-error-text ut fra invalid.

Feilmeldingen kan derfor ligge i markupen hele tiden, skjult til feltet er ugyldig.

AttributtTypeHva det gjør
invalidboolskSetter aria-invalid på kontrollen og viser feilmeldingen
disabledboolskSetter disabled på kontrollen og demper ledeteksten
optionalboolskSetter data-optional på ledeteksten
required-markernone | symbol | textSetter data-required på ledeteksten
control-idstrengBestemmer id-en på kontrollen i stedet for å la komponenten lage en
described-bystrengLegger til ekstra id-er i aria-describedby, i tillegg til hjelp og feil

required-marker er bare visuelt. required må stå på selve <input>.

<form id="kontaktskjema">
<fs-field id="navn-felt" required-marker="symbol">
<label>Fullt navn</label>
<input class="fs-input" type="text" name="navn" required />
<p class="fs-error-text">Fyll inn navnet ditt.</p>
</fs-field>
<button class="fs-button" type="submit">Send</button>
</form>
<script type="module">
import { defineFsField } from "@fristil/designsystem/field"
defineFsField()
const skjema = document.getElementById("kontaktskjema")
const navnFelt = document.getElementById("navn-felt")
const navnInput = navnFelt.querySelector("input")
skjema.addEventListener("submit", (hendelse) => {
hendelse.preventDefault()
const tomt = navnInput.value.trim() === ""
navnFelt.toggleAttribute("invalid", tomt)
if (tomt) {
navnInput.focus()
return
}
const data = new FormData(skjema)
console.log(Object.fromEntries(data))
})
</script>

new FormData(skjema) plukker opp feltet uten videre. Det er hele poenget med at komponenten holder seg i vanlig DOM.

  • Komponenten gjør koblingen, ikke innholdet. Ledeteksten og feilmeldingen må du fortsatt skrive godt.
  • Sett required<input> i tillegg til required-marker.
  • Sett invalid når brukeren har forlatt feltet, ikke for hvert tastetrykk. Meldingen leses opp hver gang den dukker opp.
  • Én kontroll per <fs-field>. Komponenten kobler seg til den første den finner. Grupper hører hjemme i en <fieldset> med <legend>.