CSS får if-satser och funktioner

Trots att CSS (Cascading Style Sheets) har funnits i nästan 30 år har språket länge saknat funktioner för mer avancerad logik – något som de flesta programmeringsspråk erbjuder. Numera finns dock stöd för variabler, villkor och funktioner.

Datorskärm med postit-lapp där det står en handskriven kodsnutt skriven. Det står, if() @function @mixin

Det finns flera skäl till att CSS saknat if-satser och funktioner. CSS designades från början för att hantera presentationen av HTML-dokument - inte dagens avancerade webbapplikationer. CSS skulle vara enkelt och kunna användas av designers utan djupare programmeringskunskaper. Det var heller inte tanken att CSS skulle användas fristående, utan i kombination med andra webbtekniker som HTML och JavaScript.

I moderna projekt med stora kodbaser har CSS ofta upplevts som begränsat, och det är vanligt att använda verktyg som SASS, LESS eller PostCSS. Dessa gör det enklare att dela upp och återanvända CSS-kod, och minskar behovet av upprepningar.

Nu börjar dock vi dock kanske närma oss en tid när dessa verktyg inte längre behövs och gradvis kan fasas ut.

If-satser i CSS

CSS har haft villkor länge i form av att webbläsare ignorerar regler som de inte förstår. Genom lite kreativ användning av detta har utvecklare kunnat skriva regler som bara gäller för vissa webbläsare eller erbjuda fallbackvärden för äldre webbläsare som inte förstår en viss CSS-egenskap. Ett annat exempel är media querys som kan användas för att skapa regler som bara gäller om skärmen har en viss bredd eller om användaren önskar använda mörkt färgtema. Ett tredje exempel är att använda en CSS-variabel vars värde växlas mellan 0 och 1 som sedan multipliceras med egenskapers värden. Has-selektorn är ytterligare ett exempel.

Men nu har CSS alltså fått en inline if-sats. Denna kräver att ett style-, media- eller supports-uttryck används och tillför därmed egentligen ingen ny funktionalitet. If-satser i CSS kan användas för att kontrollera om en variabel har ett visst värde, om skärmens bredd har en viss storlek eller om webbläsaren har stöd för CSS Grid, men inte jämföra om värdet A är större än värdet B eller om E=mc². If-satser underlättar genom att CSS-kod kan skrivas på ett enklare sätt. Istället för att dela upp regler i olika block kan en if-sats användas direkt som värde i ett och samma block.

Exempel

Om en rubriktext ska ha 3rem stor text på stora skärmar och 2rem på små skärmar går det att uppnå på lite olika sätt men en vanlig lösning kan vara en media query.

h1 {
  font-size: 2rem;
}

@media((min-width: 900px)) {
  h1 {
    font-size: 3rem;
  }
}

Med CSS if-sats kan vi sammanfoga dessa block till ett och samma. Koden blir enklare att följa eftersom all kod för h1 font-size finns samlad.

h1 {
  font-size: if(
    media(min-width: 900px): 3rem;
    else: 2rem;
  );
}

Ett annat exempel för att växla rundade hörn av och på. Tidigare kunde det göras med CSS-variabler genom att multiplicera värdet för border-radius med 1 eller 0.

<div class="card" style="--rounded-corners: 1">

</div>

<style>
.card {
  border-radius: calc(var(--rounded-corners, 0) * 1rem);
}
</style>

Med if-satser går det istället att skriva så här:

<div class="card" style="--rounded-corners: true">

</div>

<style>
.card {
  border-radius: if(style(--rounded-corners: true): 1rem);
}
</style>

Ett ytterligare exempel är att skapa färgteman. I detta exempel styr variabeln --theme vilken färg som ska användas för text och bakgrund.

<article style="--theme:party">Party</article>
<article style="--theme:retro">Retro</article>
<article style="--theme:corp">Corporate</article>

<style>
article {
  background-color: if(
    style(--theme: party): cyan;
    style(--theme: retro): lime;
    style(--theme: corp): whitesmoke;
  );
  color: if(
    style(--theme: party): magenta;
    style(--theme: retro): black;
    style(--theme: corp): navy;
  );
  font-family: if(
    style(--theme: party): 'Comic sans MS', fantasy;
    style(--theme: retro): monospace;
    style(--theme: corp): Helvetica, sans-serif;
  );
}
</style>

Det kanske inte verkar vara jättestor skillnad mot att skapa variationer av article med flera regler som .article-retro och .article-corp. Men återigen; med if-satser kan koden skrivas i samma block istället för att delas upp i flera block.

När denna artikel skrivs har CSS if-satser endast stöd i webbläsarna Chrome och Edge och är flaggad som Baseline Limited Availability. Detta innebär att if-satser inte bör användas i produktion i dagsläget utan att använda ett verktyg som omvandlar koden.

Det verkar inte finnas en modul för PostCSS som hanterar inline if-satser, men jag gissar att det dyker upp inom kort.

Funktioner

Den kanske största anledningen till att använda verktyg som SASS eller PostCSS är för att undvika upprepning av kod. Avsaknaden av funktioner har gjort att CSS-kod kan behöva upprepas på flera ställen, även om det i vissa fall går att undvika genom arv. Sanningen är att CSS länge haft flertalet funktioner som: calc, min, max, rotate, sin, cos, tan och (icke att förglömma) atan2.

Det som har saknats är möjligheten att skapa egna funktioner och det är nu på gång! Dessa funktioner kan användas för att beräkna värden för CSS egenskaper eller variabler.

Exempel

Här är en enkel funktion som tar emot ett värde och halverar det. I CSS måste funktioner, precis som variabler, ha ett namn som börjar med två minustecken.

@function --half(--value) {
  result: calc(var(--value) / 2);
}

.box {
  width: --half(400px);
}

Förutom att använda de värden som skickas med som argument kan funktioner använda sig av variabler som definierats i det block som funktionen anropas i, men även variabler som ärvs från regler tidigare i strukturen.

I detta exempel skulle elementet med class="box1" bli 100px bred eftersom det använder värdet som deklarerats för regeln för body. Elementet med class="box2" skulle däremot bli 200px bred.

@function --half-width() {
  result: calc(var(--width) / 2);
}

body {
  --width: 200px;
}

.box1 {
  width: --half-width();
  aspect-ratio: 1;
}

.box2 {
  --width: 400px;
  width: --half-width();
  aspect-ratio: 1;
}

Låt oss titta på någonting mer användbart som en funktion för att skapa randiga bakgrunder. CSS-funktioner kan ta emot argument med standardvärden och dessa blir då frivilliga att ange.

@function --striped-bg(--color1, --color2, --width: 1rem, --angle: 45deg) {
  result: repeating-linear-gradient(
    var(--angle),
    var(--color1),
    var(--color1) var(--width),
    var(--color2) var(--width),
    var(--color2) calc(var(--width) * 2)
  );
}

.box1 {
  width: 20rem;
  aspect-ratio: 1;
  background-image: --striped-bg(red, blue);
}

.box2 {
  width: 20rem;
  aspect-ratio: 1;
  background-image: --striped-bg(yellow, black, 2rem, -45deg);
}

Ännu mer användbart är att CSS funktioner kan innehålla CSS-uttryck som media, supports eller style. Då media querys inte kan använda CSS-variabler kan det vara en god idé att använda media querys i en återanvändbar funktion för att undvika upprepning av hårdkodade magiska nummer i koden. 

Här är ett exempel på en funktion som tar emot tre värden för olika viewport-bredder. Funktionen kan återanvändas för olika CSS-egenskaper för att skapa ett responsivt gränssnitt.

@function --responsive-value(--small, --medium, --large) {
  --value: var(--small);
  
  @media (min-width: 768px) {
    --value: var(--medium);
  }
  
  @media (min-width: 1200px) {
    --value: var(--large);
  }
  
  result: var(--value);
}

body {
  font-size: --responsive-value(1rem, 1.5rem, 2rem);
}

.card {
  background: antiquewhite;
  padding: --responsive-value(.5rem, 1rem, 1.5rem);
  border-radius: --responsive-value(.25rem, .5rem, 1rem);
}

.icon {
  width: --responsive-value(1rem, 1.25rem, 1.5rem);
  aspect-ratio: 1;
}

CSS funktioner kan alltså hjälpa till att förenkla utveckling genom enklare återanvändning av kod och därmed undvika upprepning, men de kan inte beräkna primtal eller sortera en lista med värden.

Funktioner har idag endast stöd i webbläsarna Chrome och Edge och har ännu inte blivit Baseline och anses därmed som experimentell.

Det verkar inte finnas en modul för PostCSS som hanterar CSS funktioner. Det finns däremot moduler för att skapa funktioner, men de fungerar mer likt traditionella funktioner.

Fler funktioner

Visste du att CSS nu har funktioner för att ta reda på vilket syskon-index ett element har samt hur många syskon-element som finns?

CSS är även på väg att få en random-funktion för att generera slumpmässiga värden direkt i CSS-kod. 

Mixins och apply

Även om if-satser och funktioner kan vara användbara är vad många egentligen efterfrågar mixins - ett sätt att återanvända grupper av CSS-definitioner. W3C:s specifikation för funktioner heter ”CSS Functions and Mixins Module” men i dagsläget finns ingen information om mixins mer än en hänvisning till att det kommer att läggas till senare. Det har tidigare funnits förslag på en apply-funktion som används för att applicera en CSS-regels definitioner på en annan regel. Denna verkar dock ha övergetts för mixins.

Med mixins går det att skapa återanvändbara grupper av CSS definitioner. Hur mixins kommer fungera i CSS är alltså inte klart. Här är ett exempel där jag drömmer om hur det skulle kunna användas för att skapa variationer av knappar.

@mixin --make-button(--color, --bg-color, --border-color, --size: medium, --hover-mix-color: black, --hover-mix-amount: 10%) {
  padding: if(
    style(--size: large): 1rem;
    style(--size: small): .5rem;
    else: .75rem;
  );
  background-color: var(--bg-color);
  color: var(--color);
  border: solid max(1px, .2rem) var(--border-color);
  font-size: 1.2rem;
  
  &:hover {
    background-color: color-mix(in srgb-linear, var(--bg-color), var(--hover-mix-color) var(--hover-mix-amount));
  }
}

.button-primary {
  --make-button(white, black, black, large);
}

.button-close {
  --make-button(black, gray, darkgray);
}

I väntan på mixins går samma sak att uppnå genom att använda CSS-variabler, det blir dock något mera kod.

[class*="button"] {
  --button-color: var(--color-base);
  --button-bg-color: var(--bg-color-base);
  --button-border-color: var(--border-color);
  --button-padding: .75rem;
  --button-hover-mix-color: black;
  --button-hover-mix-amount: 10%;
  
  padding: var(--button-padding);
  background-color: var(--button-bg-color);
  color: var(--button-color);
  border: solid max(1px, .2rem) var(--button-border-color);
  font-size: 1.2rem;
  
  &:hover {
    background-color: color-mix(in srgb-linear, var(--button-bg-color), var(--button-hover-mix-color) var(--button-hover-mix-amount));
  }
}

.button-large {
  --button-padding: 1rem;
}

.button-small {
  --button-padding: .5rem;
}

.button-primary {
  --button-color: white;
  --button-bg-color: var(--color-primary);
  --button-border-color: var(--color-primary);
  --button-size: large;
}

.button-close {
  --button-color: black;
  --button-bg-color: gray;
  --button-border-color: darkgray;
}

Det är tydligt att if-satser, funktioner och mixins alla erbjuder snarlik funktionalitet och kommer säkert att användas i kombination med varandra. De tillför egentligen ingenting helt nytt till CSS men kommer underlätta utveckling genom mindre och enklare kod som blir tydligare. Mindre och enklare kod leder till snabbare utveckling och mindre buggar!

Att fasa ut verktyg som SASS, LESS och PostCSS förenklar också utveckling och sänker kunskapströskeln. I väntan på bättre webbläsarstöd behöver vi troligen förlita oss på liknande verktyg ännu ett tag framöver. 

Publicerad:
Andreas Nylin, porträtt

Andreas Nylin

Senior gränssnittsutvecklare och tillgänglighetsexpert