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. En förklaring är att CSS fungerar på ett helt annat sätt än traditionella programmeringsspråk och aldrig varit avsett att användas fristående, utan i kombination med andra webbtekniker som HTML och JavaScript.

I större webbprojekt har CSS därför ofta upplevts som begränsat, och det har blivit vanligt att använda verktyg som SASS, LESS eller PostCSS. Dessa gör det enklare att dela upp och återanvända kod, samtidigt som de minskar behovet av upprepningar.

Numera har dock CSS inbyggt stöd för variabler, villkor och funktioner. Därmed närmar sig språket en nivå där preprocessorer kanske inte längre behövs och gradvis kan fasas ut.

If-satser i CSS

På sätt och vis har CSS haft villkor länge i form av att webbläsare ignorerar regler som de inte förstår. Genom lite kreativ användning 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 där olika regler kan appliceras beroende på skärmens storlek eller vilket tema som användaren föredrar. Ett tredje exempel är att använda CSS-variabler. Det går som exempel att växla värden av eller på genom att multiplicera värdet med 1 eller 0. 

Men nu har CSS alltså fått stöd för inline if-satser. Dessa kräver att en style, media eller supports-query 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 på så sätt 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.

<article style="--rounded-corners: 1">

</article>

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

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

<article style="--rounded-corners: true">

</article>

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

Ett ytterligare exempel är att skapa färgteman. Förutom att användas som värde för egenskaper kan if-satser också användas för variabler. 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 har haft funktioner länge som: calc, min, max, attr, 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 som argument till funktioner kan funktioner använda sig av variabler som satts i det block som funktionen anropas i, eller variabler som ärvs från regler tidigare i strukturen.

I detta exempel skulle elementet med class="box1" bli 100px bred eftersom den använder värdet som deklarerats 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();
}

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

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;
  margin: 1rem;
  background-image: --striped-bg(red, blue);
}

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

Ännu mer användbart är att CSS funktioner kan innehålla CSS querys 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. Vi kan återanvända funktionen för att ange värde för olika CSS-egenskaper och 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 att återanvända kod och 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, men jag gissar att det dyker upp inom kort. 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 fungera 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 detta uppnå med CSS-variabler, det blir dock något mera kod.

.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 tillsammans. De tillför egentligen ingenting nytt till CSS men kommer underlätta utveckling genom enklare, tydligare och mindre kod. 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. Men troligen behöver vi förlita oss på liknande verktyg ännu ett tag framöver. 

Publicerad: