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 server | fs.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.
Hvorfor koblingen er vanskelig
Section titled “Hvorfor koblingen er vanskelig”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.
Som funksjon
Section titled “Som funksjon”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.
Som element
Section titled “Som element”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.
Kom i gang
Section titled “Kom i gang”// main.ts, kjøres én gang når appen starterimport { 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.
Vi sender kvittering til denne adressen.
Skriv en e-postadresse med krøllalfa.
Du kan skrive opptil 500 tegn.
<fs-field required-marker="text" invalid> <label>E-postadresse</label> <input class="fs-input" type="email" value="ola@" /> <p class="fs-help-text">Vi sender kvittering til denne adressen.</p> <p class="fs-error-text">Skriv en e-postadresse med krøllalfa.</p></fs-field>
<fs-field optional> <label>Melding til saksbehandler</label> <textarea class="fs-textarea" rows="3"></textarea> <p class="fs-help-text">Du kan skrive opptil 500 tegn.</p></fs-field><label> har ikke for, og tekstene har ikke id. Komponenten fyller ut begge deler.
Enkleste eksempel
Section titled “Enkleste eksempel”<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.
Automatisk kobling
Section titled “Automatisk kobling”Hver gang innholdet eller attributtene endrer seg, finner den <label> og første kontroll blant barna og:
- Gir kontrollen en
idhvis den mangler, og setterlabel[for]til den. - Gir hjelpetekst og feilmelding
idved behov. - Setter
aria-describedbypå kontrollen, med begge tekstene når begge finnes. - Setter
aria-invalid="true"på kontrollen nårinvalider satt. - Setter
data-requiredellerdata-optionalpå ledeteksten ut fra attributtene på<fs-field>. - Viser eller skjuler
fs-error-textut frainvalid.
Feilmeldingen kan derfor ligge i markupen hele tiden, skjult til feltet er ugyldig.
Attributter
Section titled “Attributter”| Attributt | Type | Hva det gjør |
|---|---|---|
invalid | boolsk | Setter aria-invalid på kontrollen og viser feilmeldingen |
disabled | boolsk | Setter disabled på kontrollen og demper ledeteksten |
optional | boolsk | Setter data-optional på ledeteksten |
required-marker | none | symbol | text | Setter data-required på ledeteksten |
control-id | streng | Bestemmer id-en på kontrollen i stedet for å la komponenten lage en |
described-by | streng | Legger til ekstra id-er i aria-describedby, i tillegg til hjelp og feil |
required-marker er bare visuelt. required må stå på selve <input>.
I en app
Section titled “I en app”<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.
// main.tsx, kjøres én gang når appen starterimport { defineFsField } from "@fristil/designsystem/field"import "@fristil/designsystem/field.css"
defineFsField()// src/fristil.d.ts, gir <fs-field> typer i JSXimport "@fristil/designsystem/react-jsx"import { fs } from "@fristil/designsystem/react"import { useState } from "react"
export function Kontaktskjema() { const [navn, setNavn] = useState("") const [berørt, setBerørt] = useState(false)
const ugyldig = berørt && navn.trim() === ""
return ( <form onSubmit={(e) => e.preventDefault()}> <fs-field required-marker="symbol" invalid={ugyldig || undefined}> <label>Fullt navn</label> <input {...fs.input({ type: "text" })} name="navn" value={navn} onChange={(e) => setNavn(e.target.value)} onBlur={() => setBerørt(true)} /> <p {...fs.errorText()}>Fyll inn navnet ditt.</p> </fs-field>
<button {...fs.button()} type="submit">Send</button> </form> )}Trenger feltet bare å vises i én tilstand, gjør fs.field() koblingen ved bygging. Da sendes ingen JavaScript for feltet i det hele tatt:
---import { fs } from "@fristil/designsystem"import "@fristil/designsystem/tokens.css"import "@fristil/designsystem/field.css"
const navn = fs.field({ id: "navn", required: "symbol", error: true })---
<label {...navn.label}>Fullt navn</label><input {...fs.input({ type: "text" })} {...navn.control} name="navn" required /><p {...fs.errorText()} {...navn.error}>Fyll inn navnet ditt.</p>Skal feltet kunne bli ugyldig mens brukeren står i det, bruker du <fs-field> og registrerer den i en <script>-tagg. Astro pakker den og kjører den på klienten:
---import "@fristil/designsystem/tokens.css"import "@fristil/designsystem/field.css"---
<fs-field 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>
<script> import { defineFsField } from "@fristil/designsystem/field" defineFsField()</script>Datastar legger reaktivitet på vanlig HTML med data-*-attributter. Ett skript, ingen byggesteg.
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.3/bundles/datastar.js"></script><form data-signals="{navn: '', beroert: false}" data-computed:ugyldig="$beroert && $navn.trim() === ''"> <fs-field required-marker="symbol" data-attr:invalid="$ugyldig"> <label>Fullt navn</label> <input class="fs-input" type="text" name="navn" data-bind:navn data-on:blur="$beroert = true" /> <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()</script>data-attr:invalid setter attributtet når uttrykket er sant og fjerner det når det er usant, nøyaktig slik <fs-field> forventer av et boolsk attributt. Ingen av forbeholdene fra React-fanen gjelder her.
Tilgjengelighet
Section titled “Tilgjengelighet”- Komponenten gjør koblingen, ikke innholdet. Ledeteksten og feilmeldingen må du fortsatt skrive godt.
- Sett
requiredpå<input>i tillegg tilrequired-marker. - Sett
invalidnå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>.