Gå til innholdet

Eget tema

Fristil har sine egne kulører. Har organisasjonen din andre, kan du skrive cellene for hånd, men da eier du kontrastløftene selv: hver farge inngår i flere par, og hvert par må holde kravet i både lyst og mørkt tema. fristil sjekk-tema sier fra hvis ett ryker.

Generatoren tar merkefargene dine og bygger resten.

Terminal window
npx @fristil/designsystem tema \
--aksent=#7c3aed \
--fare=#b3261e \
--suksess=#2b6940 \
--advarsel=#8a5a00 \
--noytral=#1a1a1a \
--ut=tema.css

Eller med en fil, som kan ligge i repoet. Kall den fristil.tema.json:

{
"aksent": "#7c3aed",
"fare": "#b3261e",
"suksess": "#2b6940",
"advarsel": "#8a5a00",
"noytral": "#1a1a1a"
}
Terminal window
npx @fristil/designsystem tema fristil.tema.json --ut=tema.css

Alle er valgfrie. En familie du utelater arver Fristils egen kulør.

FamilieBrukes til
--aksentLenker, knapper og fokusmarkering
--fareFeil, sletting og avslag
--suksessFullført og godkjent
--advarselNoe som krever oppmerksomhet
--noytralSiden, brødteksten og kantene. Nesten uten kulør
--besoktBesøkte lenker
--merke1, --merke2, --merke3Flater, seksjoner og kategorier. De betyr ikke noe i seg selv

Legg fila etter tokens.css. Den overstyrer det oppskriften nevner, og resten kommer fortsatt derfra:

@import "@fristil/designsystem/tokens.css";
@import "./tema.css";

Temaet dekker både lyst og mørkt tema, og både systemvalget og et eksplisitt data-theme.

color-scheme avgjør hvordan nettleseren tegner sine egne flater: nedtrekkslista til en <select>, rullefelt og kalenderpanelet i et datofelt. Uten den blir de lyse i en mørk side.

Fristil setter den der noen har valgt et tema: på [data-theme="light"], på [data-theme="dark"] og på de tre valgene til temavelgeren. Den står ikke på bar :root, for egenskapen arves nedover, så en verdi der gjelder hele dokumentet. Et barn kan melde seg ut med color-scheme: normal, men det er en motregel verten aldri ba om å 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;
}

Bruker du temavelgeren, skal du ikke skrive den linja. Hvert av de tre valgene setter color-scheme selv. 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.

Den fyller matrisen. Hver familie du oppgir får de ni rollene, og hver rolle får sin faste lyshet. Gir du bare noen av familiene, arver resten Fristils egne kulører.

Kuløren og metningen er dine, lysheten er rollens. Gir du en lys lilla eller en mørk lilla, får du den samme lilla familien: rollen bestemmer hvor lys hver celle er, og det er det som gjør at kontrasten kan garanteres. Metningen følger med, så en dus merkefarge gir en dus familie.

Regningen skjer i OKLCH og ikke i HSL. I HSL betyr lyshet noe ulikt for hver kulør: hsl(60 100% 50%) er knallgul og hsl(240 100% 50%) er nesten sort, med samme tall. I OKLCH er lysheten den samme opplevde lysheten uansett kulør, så én lyshet per rolle kan gjelde for alle farger.

Kontrasten er garantert, ikke justert. Lyshetene er regnet fram mot et sveip rundt hele fargesirkelen, på den høyeste metningen sRGB kan vise for hver kulør. Derfor holder løftene uansett hvilken merkefarge du gir, og generatoren trenger ikke flytte fargen din i etterkant. Den gjorde det før, hver eneste gang, fordi ett trinn var valgt for lyst.

Gir du likevel en farge som ikke kan bære rollene sine, for eksempel en nesten hvit merkefarge, avsluttes kjøringen med feil framfor å levere et tema som ser riktig ut.

Den velger ikke farger for deg. Gir du en kulør som ligner for mye på en annen, for eksempel en oransje fare og en gul advarsel, blir de to like i bruk selv om begge holder kontrastkravet. Fargene må være til å skille fra hverandre, og det er en vurdering, ikke en utregning.

Den rører heller ikke ikonene i tokens.css. De er tegnet i nøytrale farger og virker i begge temaer.

Fargene er det vanskeligste å få riktig, og det er derfor generatoren begynte der. Men to systemer med de samme kulørene ser fortsatt ulike ut hvis skriften og hjørnene er ulike. Derfor kan oppskriften også si hvordan det skal se ut:

{
"aksent": "#1362ae",
"fare": "#a82e39",
"suksess": "#316f2a",
"advarsel": "#9f7509",
"typografi": {
"fontFamily": "Helvetica, Arial, sans-serif",
"weights": { "bold": 700 },
"lineHeights": { "default": 1.666, "heading": 1.333, "article": 1.666 }
},
"form": {
"buttonRadius": "2.75rem",
"fieldRadius": "0.25rem",
"surfaceRadius": "0.5rem",
"buttonBorderWidth": "3px",
"buttonFontWeight": 700
}
}

Seks av verdiene finnes også som flagg: --skrift, --knapp-hjorner, --felt-hjorner, --flate-hjorner, --knapp-ramme og --knapp-vekt. Vektene og linjeavstandene settes bare fra fil. En kommandolinje med åtte skrifttall er ikke til å lese, og typografi er dessuten noe man bestemmer én gang og beholder, altså nettopp det en fil i repoet er til for.

Et flagg som ikke finnes stopper kjøringen framfor å bli ignorert, så en skrivefeil sier fra. Det samme gjelder en nøkkel i oppskriftsfila. Unntaket er $schema, som editorer skriver inn av seg selv og som ikke sier noe om temaet.

Blokknavnene kan stå på norsk eller engelsk, som fargene. Nøklene inni er engelske, og en nøkkel som ikke finnes stopper kjøringen framfor å bli ignorert:

NøkkelHva den setter
typografi.fontFamilyskriftstakken, som token og som regel på :root
typografi.weights.regular · .medium · .semibold · .boldde fire vektene
typografi.lineHeights.defaultkontroller: knapp, felt, tabell
typografi.lineHeights.headingoverskrifter
typografi.lineHeights.articlebrødtekst og lister
typografi.lineHeights.compactsmå flater: hjelpetekst, hjelpeboble, korttittel
form.buttonRadiusknappen, paginering og hopplenken
form.fieldRadiusfeltet, tekstområdet og nedtrekkslista
form.surfaceRadiuskort, dialog, sprettoppvindu, varsel, trekkspill, feiloppsummering, filopplasting, økttidsvarsel, forslagslista, meldingen og hjelpeboblen
form.buttonBorderWidthrammetykkelsen på knappen
form.buttonFontWeightvekten på knappeteksten

Alt her er valgfritt. Utelater du begge blokkene, er temaet nøyaktig det det var før: bare farger.

Bruker organisasjonen din allerede Fristils kulører, er det skriften og hjørnene som skiller. Da utelater du merkefargene helt, og får et tema som lar fargene stå:

Terminal window
npx @fristil/designsystem tema \
--skrift="Helvetica, Arial, sans-serif" \
--knapp-hjorner=2.75rem \
--ut=tema.css

Et tema helt uten innhold kaster. Oppgi enten en farge, en skrift eller en form.

Ett felles tall ville vært enklere og feil. Et annet norsk designsystem har helt runde knapper og felt med nesten rette hjørner, og med ett tall blir feltene kapsler.

Avkryssingsboksen, merket, etiketten, valggruppa, avataren og skjelettet står utenfor. Der er hjørnet ikke et stilvalg, men selve formen: en avkryssingsboks som blir rund, ser ut som en radioknapp, og et merke som blir firkantet, ser ut som en knapp.

Den genererte fila legger seg i sitt eget lag, erklært etter pakkens eget:

@layer fristil, fristil-tema;

Erklæringen avgjør rekkefølgen uavhengig av når stilarkene lastes, og det er ikke en detalj: både Astro og TanStack Start legger sin bundlede CSS inn rett før </head>, altså etter en <link> du selv har skrevet. Lå temaet i det samme laget som pakken, ville det tapt mot standardverdiene i de to. Begge lagene ligger fortsatt foran usortert CSS, så dine egne regler vinner som før.

Fristil arver skrift med vilje, så et token alene ville ikke endret én eneste bokstav. Oppgir du fontFamily, skriver generatoren derfor både tokenet og regelen som tar det i bruk:

:root {
font-family: var(--font-family-base);
}

Regelen står i @layer fristil-tema, som resten av temaet, så din egen CSS vinner fortsatt over den.

Trenger du temaet et annet sted enn på kommandolinjen, for eksempel i et byggesteg eller en temavelger:

import { buildTheme } from "@fristil/designsystem/tema"
const tema = buildTheme({
accent: "#7c3aed",
danger: "#b3261e",
success: "#2b6940",
warning: "#8a5a00",
})
tema.css // ferdig CSS
tema.light // cellene i lyst tema, som objekt
tema.dark // cellene i mørkt tema
tema.violations // løfter som ikke holder. Tom når temaet er i orden

Nøklene er familienavnene på engelsk: accent, visited, brand1 til brand3, neutral, danger, warning og success. På kommandolinjen heter de det samme på norsk.

Vil du regne selv, har @fristil/designsystem/farge regnestykkene bak: parseHex, toHex, rgbToOklch, oklchToRgb og contrastRatio. Kontrakten, altså hvilken lyshet hver rolle har og hvilke par som måles, ligger i @fristil/designsystem/kontrakt.

Løftene kjøres mot tolv merkefarger spredt rundt hele fargesirkelen, fra knallrød til nesten sort, i begge temaer. Er én celle for lys, feiler testen med hvilket par som røk og hvor langt unna det var.

Skriver du cellene for hånd framfor å generere dem, er fristil sjekk-tema tema.css den samme kontrollen på din egen fil.