Gå til innholdet

Tilpasning

Du tilpasser Fristil ved å sette CSS-variabler i din egen CSS. Verdiene som følger med pakken er standardverdier, og dine egne vinner over dem.

Tre veier, i den rekkefølgen du bør prøve dem:

VeiTil hva
TokensFarger, på tvers av hele systemet
KomponentvariablerForm og størrelse på én komponent
Vanlige reglerAlt annet

Det er én vei inn: din egen CSS, lastet etter tokens.css. Alt Fristil sender ut ligger i et cascade layer (@layer fristil), og CSS uten layer vinner alltid over CSS i et layer, uansett spesifisitet. Du trenger verken !important eller lange selektorer.

Standarden er å la være: Fristils egne farger er kontrollert mot WCAG AA i begge temaer, og du trenger ikke skrive noe. Skal du endre dem, velger du hvor grovt du tar i.

Du vilSkriv
Endre én fargeCella, --fs-color-accent-fill
Endre en hel familieDe ni rollene, --fs-color-accent-surface til --fs-color-accent-content
Gi hele systemet organisasjonens fargerIngenting. Lag et tema, så skrives fila for deg

Generatoren er ikke en egen mekanisme. Den setter de samme variablene, for begge temaer. Fila den skriver legger seg i sitt eget lag, fristil-tema, slik at din egen CSS fortsatt vinner over temaet.

Skriver du celler selv, er kontrastløftene dine å holde. fristil sjekk-tema tema.css sier hvilken celle som ryker og hvorfor.

:root {
--fs-color-accent-fill: #7c3aed;
--fs-color-accent-content: #ffffff;
}

Uten at du gjør noe, følger fargene maskinens innstilling. En side som vil bestemme selv setter data-theme på <html>:

<html data-theme="light">

data-theme virker på et hvilket som helst element, ikke bare på roten. Elementet og alt under det får temaet, og resten av siden står urørt:

<div data-theme="dark">
<button class="fs-button">Denne er mørk</button>
</div>

Et tema kan ligge inne i et annet, begge veier. En lys boks i en mørk seksjon er like gyldig som det motsatte.

En innebygd komponent låser sitt eget tema

Section titled “En innebygd komponent låser sitt eget tema”

Legger du Fristil inn i en side du ikke eier, er dette måten å gjøre det på. Sett attributtet på komponentens eget rotelement, så er den lys uansett hva maskinen står på, og verten røres ikke:

<div data-theme="light">
<table class="fs-table">
<caption class="fs-sr-only">Fakturaer</caption>
</table>
</div>

Du skal ikke sette data-theme på vertens <html>. Det er et globalt attributt som tilhører siden, ikke deg.

fs-theme-control på en radioknapp gjør value til et temavalg, i ren CSS:

<label class="fs-toggle-group__option">
<input class="fs-theme-control" type="radio" name="tema"
value="dark" /> Mørkt
</label>

Tre verdier betyr noe: auto, light og dark. light og dark vinner over data-theme på <html>, så serveren kan sende det lagrede valget mens et klikk slår igjennom med én gang. auto har ingen temablokk, så der står serverens data-theme igjen. Kontrollen lagrer ingenting selv. Se La brukeren velge tema for hele mønsteret.

color-scheme avgjør hvordan nettleseren tegner sine egne flater: rullefelt, nedtrekkslista til en <select> og kalenderpanelet i et datofelt. Fristil setter den der noen har valgt et tema: på [data-theme="light"], på [data-theme="dark"] og på de tre valgene til temavelgeren. Bar :root får den aldri.

Grunnen er at egenskapen arves nedover, så en verdi på roten gjelder hele dokumentet, også en vertsside som aldri ba om det. Et barn kan melde seg ut med color-scheme: normal, men det er en motregel verten ikke skal måtte skrive. Vil du at nettleserens flater skal følge systemet på hele siden, skriver du én linje selv:

:root {
color-scheme: light dark;
}

Det er den samme avtalen som for lagrekkefølgen: én linje hver, og begge er dine.

Bruker du temavelgeren, skal du ikke skrive den linja. Hvert av de tre valgene setter color-scheme selv: light gir light, dark gir dark, og «Følg systemet» gir light dark, altså beskjeden om at siden fungerer i begge og at nettleseren kan velge etter systemet. Skriver du den likevel, utenfor et lag, slår regelen @layer fristil, og nettleserens flater blir låst til systemet mens brukeren har valgt noe annet. Å legge den i et lag foran fristil hjelper ikke: rekkefølgen er alt etablert av tokens.css, som lastes først, så en senere @layer-setning legger laget bak.

Overstyringen under «Egne farger» gjelder begge temaer. Vil du ha ulike verdier, skriver du dine egne regler:

:root {
--fs-color-accent-fill: #7c3aed;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--fs-color-accent-fill: #c4b5fd;
}
}
[data-theme="light"] {
--fs-color-accent-fill: #7c3aed;
}
[data-theme="dark"] {
--fs-color-accent-fill: #c4b5fd;
}

Bruker du temavelgeren, må de to siste blokkene ha dens selektor ved siden av, slik Fristils egne har:

[data-theme="light"],
:root:has(.fs-theme-control[value="light"]:checked) {
--fs-color-accent-fill: #7c3aed;
}
[data-theme="dark"],
:root:has(.fs-theme-control[value="dark"]:checked) {
--fs-color-accent-fill: #c4b5fd;
}

Velgeren setter ikke data-theme, for CSS kan ikke skrive et attributt. Uten selektoren traff ingen av blokkene dine når brukeren valgte et tema, og Fristils egen farge sto igjen.

Alle fire blokkene må være med. De to data-theme-blokkene deklarerer hver sin verdi rett på elementet som bærer attributtet, og en deklarasjon på elementet selv vinner over en verdi det arver, uansett hvilket lag arven kom fra. Utelater du [data-theme="light"], står Fristils egen farge inne i en lys temagrense mens din gjelder ellers på siden.

Farger er ikke det eneste du kan endre. Hver komponent har egne variabler for form og størrelse:

:root {
--fs-button-radius: 9999px;
--fs-input-radius: 0;
}

Hvor du setter den avgjør hvor langt den rekker

Section titled “Hvor du setter den avgjør hvor langt den rekker”

Variablene arves, som alle CSS-variabler. Stedet du skriver deklarasjonen er derfor det eneste som bestemmer rekkevidden, og du trenger ingen egen mekanisme for å begrense den.

På :root gjelder den hele appen, som i eksempelet over. Skriver du den på din egen klasse i stedet, gjelder den det elementet og alt som ligger inni:

Markupen er den samme i begge. Den eneste forskjellen er klassen tett-panel, og den er din, ikke vår. Knappen inni blir smalere uten at du rører knappen: den arver variabelen fra kortet.

Det gir deg tre rekkevidder med den samme mekanismen:

Hvor du skriver denHvor langt den rekker
:rootHele appen
Din egen klasse på en beholderAlt inni den beholderen
Din egen klasse på komponentenBare den ene

Tabellen under er et oppslagsverk over hver variabel som finnes. Hva den enkelte gjør, og et eksempel som viser det, står på komponentens egen side.

VariabelStandard
--fs-button-paddingvar(--fs-spacing-2) var(--fs-spacing-4)
--fs-button-radiusvar(--fs-spacing-1)
--fs-button-border-width1px
--fs-button-font-weightvar(--font-weight-medium)
--fs-button-line-heightvar(--fs-line-height-default)
--fs-heading-font-weightvar(--font-weight-bold)
--fs-heading-line-heightvar(--fs-line-height-heading)
--fs-paragraph-line-heightvar(--fs-line-height-article)
--fs-badge-paddingvar(--fs-spacing-0-5) var(--fs-spacing-2)
--fs-badge-radiusvar(--fs-spacing-4)
--fs-alert-paddingvar(--fs-spacing-3) var(--fs-spacing-4)
--fs-alert-radiusvar(--fs-spacing-1)
--fs-alert-accentvar(--fs-spacing-1)
--fs-card-paddingvar(--fs-spacing-4)
--fs-card-radiusvar(--fs-spacing-2)
--fs-divider-spacevar(--fs-spacing-4)
--fs-spinner-sizevar(--fs-spacing-6)
--fs-spinner-thickness2px
--fs-table-cell-paddingvar(--fs-spacing-2) var(--fs-spacing-3)
--fs-list-gapvar(--fs-spacing-1), men var(--fs-spacing-2) i divided
--fs-tag-paddingvar(--fs-spacing-0-5) var(--fs-spacing-2)
--fs-tag-radiusvar(--fs-spacing-1)
--fs-avatar-sizevar(--fs-spacing-10)
--fs-avatar-radiusvar(--fs-spacing-1)
--fs-skeleton-heightvar(--fs-spacing-4), men var(--fs-spacing-10) i circle
--fs-skeleton-radiusvar(--fs-spacing-1)
--fs-breadcrumbs-gapvar(--fs-spacing-2)
--fs-pagination-gapvar(--fs-spacing-1)
--fs-pagination-sizevar(--fs-spacing-10)
--fs-pagination-radiusvar(--fs-spacing-1)
--fs-progress-fillvar(--fs-color-accent-fill), og data-color endrer den
--fs-progress-trackvar(--fs-color-neutral-surface)
--fs-progress-heightvar(--fs-spacing-2)
--fs-progress-radiusvar(--fs-spacing-1)
--fs-skip-link-paddingvar(--fs-spacing-2) var(--fs-spacing-4)
--fs-skip-link-radiusvar(--fs-spacing-1)
--fs-accordion-radiusvar(--fs-spacing-1)
--fs-accordion-paddingvar(--fs-spacing-3) var(--fs-spacing-4)
--fs-dialog-widthmin(32rem, 100%, calc(100vw - var(--fs-spacing-8)))
--fs-dialog-paddingvar(--fs-spacing-5)
--fs-dialog-layer40
--fs-dialog-radiusvar(--fs-spacing-2)
--fs-tooltip-width16rem
--fs-tooltip-paddingvar(--fs-spacing-1) var(--fs-spacing-2)
--fs-tooltip-radiusvar(--fs-spacing-1)
--fs-paragraph-width70ch
--fs-paragraph-gapvar(--fs-spacing-3)
--fs-tabs-gapvar(--fs-spacing-1)
--fs-tabs-paddingvar(--fs-spacing-2) var(--fs-spacing-4)
--fs-tabs-panel-spacevar(--fs-spacing-4)
--fs-error-summary-paddingvar(--fs-spacing-4)
--fs-error-summary-radiusvar(--fs-spacing-1)
--fs-file-upload-paddingvar(--fs-spacing-2)
--fs-file-upload-radiusvar(--fs-spacing-1)
--fs-avatar-stack-overlapcalc(var(--fs-spacing-2) * -1)
--fs-popover-widthmin(20rem, calc(100vw - var(--fs-spacing-8)))
--fs-popover-paddingvar(--fs-spacing-3)
--fs-popover-radiusvar(--fs-spacing-1)
--fs-popover-topsettes av komponenten
--fs-popover-leftsettes av komponenten
--fs-toast-spacevar(--fs-spacing-4)
--fs-toast-widthmin(24rem, calc(100vw - var(--fs-spacing-8)))
--fs-toast-paddingvar(--fs-spacing-3) var(--fs-spacing-4)
--fs-toast-radiusvar(--fs-spacing-1)
--fs-toast-layer60
--fs-suggestion-max-heightcalc(var(--fs-spacing-10) * 4)
--fs-suggestion-radiusvar(--fs-spacing-1)
--fs-suggestion-layer30
--fs-suggestion-option-paddingvar(--fs-spacing-2) var(--fs-spacing-3)
--fs-input-paddingvar(--fs-spacing-2) var(--fs-spacing-3)
--fs-input-radiusvar(--fs-spacing-1)
--fs-textarea-min-heightcalc(var(--fs-spacing-4) * 5)
--fs-textarea-paddingvar(--fs-spacing-2) var(--fs-spacing-3)
--fs-textarea-radiusvar(--fs-spacing-1)
--fs-select-radiusvar(--fs-spacing-1)
--fs-select-option-paddingvar(--fs-spacing-2) var(--fs-spacing-3)
--fs-select-viewport-marginvar(--fs-spacing-2)
--fs-checkbox-sizevar(--fs-spacing-5)
--fs-checkbox-radiusvar(--fs-spacing-1)
--fs-radio-sizevar(--fs-spacing-5)
--fs-switch-widthvar(--fs-spacing-10)
--fs-switch-heightvar(--fs-spacing-6)
--fs-fieldset-gapvar(--fs-spacing-2)
--fs-search-icon-spacevar(--fs-spacing-10)
--fs-search-icon-sizevar(--fs-spacing-5)
--fs-toggle-group-radiusvar(--fs-spacing-1)
--fs-toggle-group-heightvar(--fs-spacing-10)
--fs-toggle-group-padding0 var(--fs-spacing-4)
--fs-session-timeout-width26rem
--fs-session-timeout-paddingvar(--fs-spacing-5)
--fs-session-timeout-radiusvar(--fs-spacing-1)
--fs-connection-status-layer1000
--fs-connection-status-paddingvar(--fs-spacing-2) var(--fs-spacing-4)

Standardverdiene peker på --fs-spacing-*-skalaen. Endrer du skalaen, følger komponentene med. Setter du variabelen, gjelder din verdi bare for den komponenten.

Alt Fristil sender ut, både tokens og komponent-CSS, ligger i @layer fristil. CSS uten layer vinner alltid over CSS i et layer, uansett spesifisitet. Du kan derfor overstyre hva som helst med en enkel selektor:

.fs-button {
letter-spacing: 0.02em;
}

Uten layeret ville du måttet slå selektorer som .fs-button[data-variant="danger"]:hover, eller ty til !important.

Ett unntak finnes, og det kommer av nettleseren og ikke av oss. En <dialog> uten open skjules av nettleserens eget stilark, og lagene sorterer bare innenfor ett opphav, så en hvilken som helst forfatterregel med display slår den skjulingen. Setter du display på .fs-dialog, blir en lukket dialog stående som et kort på siden. Ta da med regelen selv, minst like spesifikk som din egen:

dialog.fs-dialog:not([open]):not(:is(:popover-open)) {
display: none;
}

Fristil setter selv display: flex når .fs-dialog__body er et direkte barn, slik at kroppen kan rulle, og har den regelen ved siden av.

Bruker du Tailwind, må laget vårt plasseres. Lagene teller i den rekkefølgen de først blir nevnt, og det siste vinner uansett spesifisitet. Sett rekkefølgen på første linje i CSS-en din:

@layer theme, base, fristil, components, utilities;
@import "tailwindcss";
@import "@fristil/designsystem/tokens.css";
@import "@fristil/designsystem/tailwind.css";

Uten den linja får du ett av to problemer, avhengig av hvilken import som kommer først:

RekkefølgeHva som skjer
fristil før baseTailwinds Preflight nullstiller komponentene. Bakgrunnen forsvinner fra knappen.
fristil etter utilitiesKomponentene slår Tailwind-klassene dine. p-6 på en .fs-button gjør ingenting.
base, fristil, utilitiesBegge deler virker

Det første er testet, ikke antatt: tilpasning.browser.test.ts bygger det i sitt eget dokument og kontrollerer hva knappen faktisk får.

Fristils eget tema for Tailwind ligger i @fristil/designsystem/tailwind.css. Det er en vanlig CSS-fil, så pakken har ingen avhengighet til Tailwind, og din versjon er den eneste som er i bruk. Se Design tokens.

Ingen komponent i Fristil bruker shadow DOM lenger. <fs-calendar> var den eneste, og den er fjernet til fordel for nettleserens eget datopanel. Se Dato i et skjema.

Det betyr at CSS-en din når fram overalt. Du trenger verken ::part() eller å lete etter en vei inn i en skyggerot.

Variabler og laget dekker utseendet. De dekker ikke at komponenten skal gjøre noe annet enn den gjør. Da kan du overta kildekoden til den ene komponenten:

Terminal window
npx @fristil/designsystem overta button --ut=src/ui

Kommandoen kopierer komponentens filer inn i prosjektet ditt, og skriver om henvisningene ut av mappa, slik at kopien virker der den havner:

Kopierte button til src/ui/button/
button.ts
button.css
Komponenten er nå din. Oppdateringer av @fristil/designsystem rører den ikke.
Henvisninger ut av mappa peker nå på pakken:
../shared.js → @fristil/designsystem/shared
Bytt ut disse importene med kopien:
@fristil/designsystem/button
@fristil/designsystem/button.css

Klassenavnene følger med. Importerer du stilarket fra pakken i tillegg, finnes komponenten to ganger, og hvilken som vinner avgjøres av rekkefølgen. Bytt derfor ut importene, som utskriften sier.

Det du gir fra deg er oppdateringene. Rettelser og forbedringer i pakken treffer ikke kopien, og det er du som eier den derfra. Bruk kommandoen på den ene komponenten som trenger det, ikke på alle.

Tilgjengeligheten blir din. Testene våre kjører mot pakken, ikke mot kopien din, så en tilgjengelighetserklæring som viser til designsystemet dekker ikke en komponent du har overtatt og endret.

npx @fristil/designsystem overta uten navn lister komponentene. Kopien skrives ikke over hvis mappa finnes fra før, med mindre du legger til --overskriv=ja.