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.
Slik tar du den i bruk
Section titled “Slik tar du den i bruk”defineFsField() registrerer web componenten i nettleseren, og kalles é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.
<!-- Stilarkene siden trenger. Uten byggesteg er det `<link>`, ikke `import`: en nettleser slår ikke opp et pakkenavn. --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/tokens/tokens.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/ramme/field/field.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/css/button/button.css"><!-- Eller alt i én fil: https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/dist/fristil.css --><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>
<!-- Én gang for hele appen, i sidemalen. Ikke én gang per komponent. --><script type="module"> import { defineFsField } from "https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/dist/components/ramme/field/fs-field.js"
defineFsField()</script>
<script type="module"> 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 av seg selv. 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/tokens.css"import "@fristil/designsystem/field.css"import "@fristil/designsystem/button.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 { fs } from "@fristil/designsystem"
import "@fristil/designsystem/tokens.css"import "@fristil/designsystem/field.css"---
<fs-field required-marker="symbol"> <label>Fullt navn</label> <input {...fs.input({ type: "text" })} name="navn" required /> <p {...fs.errorText()}>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.4/bundles/datastar.js"></script><!-- Stilarkene siden trenger. Uten byggesteg er det `<link>`, ikke `import`: en nettleser slår ikke opp et pakkenavn. --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/tokens/tokens.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/ramme/field/field.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/css/input/input.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/css/error-text/error-text.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/src/components/css/button/button.css"><!-- Eller alt i én fil: https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/dist/fristil.css --><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>
<!-- Én gang for hele appen, i sidemalen. Ikke én gang per komponent. --><script type="module"> import { defineFsField } from "https://cdn.jsdelivr.net/npm/@fristil/designsystem@0.27.1/dist/components/ramme/field/fs-field.js"
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.
fs.input() skal bare få type. Tilstanden ligger i felt.control, og de to overlapper med vilje ikke. Sendte du state til begge, ville aria-invalid blitt skrevet ut to ganger i maler som gjengir attributtene bokstavelig, som Astro og ren HTML. felt.state finnes fortsatt hvis du vil drive noe annet med den.
Merkingen
Section titled “Merkingen”required-marker velger hvordan et påkrevd felt merkes, og optional snur det om: merk det valgfrie framfor det påkrevde, når nesten alt i skjemaet må fylles ut. optional har ingen plass i oppskriften over, siden den viser ett felt og ikke et skjema.
<fs-field required-marker="text"> <label>E-postadresse</label> <input class="fs-input" type="email" /></fs-field>
<fs-field optional> <label>Melding til saksbehandler</label> <textarea class="fs-textarea" rows="3"></textarea></fs-field>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.
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: hjelpeteksten alltid, og feilmeldingen i tillegg når feltet er ugyldig. En skjult feilmelding skal ikke leses opp. - 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.
Komponenten krever to klassenavn av deg: fs-help-text på hjelpeteksten og fs-error-text på feilmeldingen. Kontrollen finner den på tagnavnet, altså <input>, <textarea> eller <select>, og et type="hidden" teller ikke som kontroll. Ledeteksten finner den på en <label> inni elementet. Står ledeteksten utenfor, må du koble de to selv: gi kontrollen en id og ledeteksten en for som peker på den.
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>.
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>.