Gå til innholdet

Rammeverk

CSS-komponentene er klasser og krever ingenting av rammeverket. Web-komponentene trenger av og til oppsett, og denne siden samler det.

Alle web-komponentene registreres på samme måte, og hver av dem har sin egen side med et eksempel i alle fire miljøene: Field, Tabs, Error Summary, Popover, Suggestion, Toast, Session Timeout og Connection Status.

Tre ting gjelder overalt:

  1. define-funksjonen må kjøre én gang når appen starter, før elementet brukes.
  2. Boolske attributter er sanne så lenge de finnes. invalid="false" gjør feltet ugyldig. Skal attributtet bort, må det fjernes helt.
  3. Serveren skriver markupen, komponenten fester oppførsel. Se Markup og oppførsel for hvorfor, og hva det betyr i hvert av miljøene.

React har i tillegg @fristil/designsystem/react, som gir className og htmlFor. Uten den skriver React «Invalid DOM property» i konsollen for hvert element. De andre bruker HTML-navnene direkte.

Ingen byggverktøy, ingen pakkebehandler i nettleseren.

<!doctype html>
<html lang="nb">
<head>
<link rel="stylesheet" href="/node_modules/@fristil/designsystem/src/tokens/tokens.css" />
<link rel="stylesheet" href="/node_modules/@fristil/designsystem/src/components/ramme/field/field.css" />
</head>
<body>
<form id="skjema">
<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>
<button class="fs-button" type="submit">Send</button>
</form>
<script type="module">
import { defineFsField } from "/node_modules/@fristil/designsystem/dist/components/ramme/field/fs-field.js"
defineFsField()
document.getElementById("skjema").addEventListener("submit", (hendelse) => {
hendelse.preventDefault()
console.log(Object.fromEntries(new FormData(hendelse.currentTarget)))
})
</script>
</body>
</html>

new FormData(...) plukker opp feltet uten videre, fordi <fs-field> holder elementene i vanlig DOM.

CSS-komponentene krever ikke engang skriptet:

<link rel="stylesheet" href="@fristil/designsystem/tokens.css" />
<link rel="stylesheet" href="@fristil/designsystem/button.css" />
<button class="fs-button" data-variant="secondary">Lagre utkast</button>

Dette er delen som overlever lengst. Klassen og dataattributtet er en kontrakt nettleseren forstår direkte.

main.tsx
import "@fristil/designsystem/tokens.css"
import "@fristil/designsystem/field.css"
import "@fristil/designsystem/suggestion.css"
import { defineFsField } from "@fristil/designsystem/field"
import { defineFsSuggestion } from "@fristil/designsystem/suggestion"
defineFsField()
defineFsSuggestion()

Bruker du CSS-komponentene, importer fs fra React-inngangen:

import { fs } from "@fristil/designsystem/react"
<button {...fs.button({ variant: "secondary" })}>Lagre utkast</button>

Importerer du dette i main.tsx før createRoot, er elementene registrert før React rekker å tegne noe.

En .astro-fil er HTML med frontmatter over. Stilarkene importeres der, og fs brukes rett i malen, siden Astro støtter spredning som JSX.

---
import { fs } from "@fristil/designsystem"
import "@fristil/designsystem/tokens.css"
import "@fristil/designsystem/button.css"
import "@fristil/designsystem/badge.css"
---
<button {...fs.button({ variant: "secondary" })}>Lagre utkast</button>
<span {...fs.badge({ color: "success" })}>Innvilget</span>

Kjøres ved bygging. Ut kommer ren HTML:

<button class="fs-button" data-variant="secondary">Lagre utkast</button>
<span class="fs-badge" data-color="success">Innvilget</span>

Null JavaScript sendt til nettleseren. fs er borte når siden er bygget.

fs.field() regner ut koblingen i frontmatteret, så den også blir statisk HTML:

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

Ingen <fs-field>, ingen kjøretid.

Skal feltet kunne bli ugyldig mens brukeren står i det, må komponenten registreres. Astro pakker <script>-tagger og kjører dem 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>

customElements finnes bare i nettleseren, så registreringen kan ikke stå i frontmatteret. Et <script> i malen er alt som skal til: komponentene er vanlige egendefinerte elementer, og trenger verken en Astro-integrasjon eller et client:-direktiv.

Astro bygger HTML-en, Datastar gir den oppførsel, og ingen av delene krever en komponentmodell i nettleseren.

Datastar legger reaktivitet på vanlig HTML med data-*-attributter. Ett skript, ingen byggesteg. Samme premiss som Fristil.

<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>

Tre attributter gjør hele jobben:

  • data-signals oppretter tilstanden.
  • data-computed:ugyldig utleder valideringen av den.
  • data-bind:navn binder feltet begge veier, og data-on:blur markerer at brukeren har forlatt det.

data-attr:invalid knytter Datastar til komponenten: den setter attributtet når uttrykket er sant og fjerner det når det er usant, nøyaktig slik <fs-field> forventer.

fs brukes eventuelt på serveren eller i byggesteget. Datastar trenger den ikke i nettleseren.

Datastar kan la serveren sende HTML underveis, over Server-Sent Events. Se @starfederation/datastar-sdk.

Et egendefinert element oppgraderer seg selv når det settes inn i dokumentet, så lenge defineFsField() har kjørt én gang. Serveren kan derfor sende dette som ren HTML:

<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 hit.</p>
<p class="fs-error-text">Skriv en gyldig adresse.</p>
</fs-field>

Komponenten kobler da for og id, setter fs-label, legger på aria-invalid og bygger aria-describedby, uten at det sendes JavaScript med for akkurat dette feltet.

Det er poenget med at ramme-komponentene bor i vanlig DOM: markupen er dataen, og tilgjengeligheten kobles der den lander.

SymptomÅrsak
Elementet vises ikke, siden ser tom utdefine-funksjonen har ikke kjørt
customElements is not definedRegistreringen kjøres under serverrendering
Feltet er alltid ugyldiginvalid="false" er satt. Attributtet må fjernes, ikke settes til false
SyntaxError i nettleserenTypeScript-syntaks i en <script type="module"> uten byggesteg
Stilene manglertokens.css er ikke importert, eller importeres etter komponentens eget stilark
React: «Invalid DOM property class»Importer fs fra @fristil/designsystem/react