CSS @layer: hvordan cascade layers avslutter krigen om spesifisitet
Du har en kortkomponent fra et designsystem-bibliotek: .card .card__header .card__title { font-size: 18px; } – tre klasser i selektoren, fordi komponentens forfatter nøstet den slik. Du vil overstyre den med én verktøyklasse, .text-lg { font-size: 20px; }, lagt til i HTML-en etter kortets klasser. Det fungerer ikke. Tittelen holder seg stivnakket på 18px, selv om .text-lg står senere i koden, så intuitivt «burde den nyeste vinne». Dette er ikke en nettleserfeil – tre klasser i en selektor slår én klasse, uansett rekkefølge i filen. Slik har spesifisitet fungert siden 1996, og slik er den ment å fungere.
Den klassiske løsningen er å øke spesifisiteten til sin egen regel – .card .text-lg, eller, når det ikke er nok, !important. Begge løsningene fungerer punktvis og bryter sammen systemisk: hver gang noen i teamet legger til en regel med enda høyere spesifisitet (eller enda en !important), blir resten av arket uforutsigbart. Det er nettopp dette problemet – ikke estetisk, men arkitektonisk – CSS Cascade Layers løser.
En ny akse for konfliktløsning, ikke enda en måte å øke prioritet på
Det avgjørende å forstå: @layer er ikke enda et verktøy for å øke en regels prioritet, slik som !important eller å legge til enda en klasse i selektoren. Det er et eget trinn i kaskade-algoritmen, avgjort før spesifisiteten, ikke ved siden av den. Den fullstendige rekkefølgen nettleseren avgjør en konflikt mellom to CSS-regler i, ser (forenklet ved å utelate origin) slik ut:
- Lag (
@layer) – en regel fra et lag deklarert senere vinner over en regel fra et lag deklarert tidligere, uansett spesifisiteten til selektorene inni. - Spesifisitet – avgjør først når begge reglene er i samme lag (eller ingen av dem er i noe lag i det hele tatt).
- Kildeorden – siste dommer, når de to foregående kriteriene ender uavgjort.
Dette gjør at én klasse .text-lg i et senere lag kan slå en selektor med tre klasser i et tidligere lag – fordi sammenstøtet aldri i det hele tatt når frem til trinnet der spesifisiteten sammenlignes. Laget avgjør saken tidligere.
css
/* Lagrekkefølgen erklærer du én gang, på forhånd – den trenger ikkesamsvare med rekkefølgen innholdet deres faktisk vises i filene */@layer reset, base, components, utilities;@layer components {.card .card__header .card__title {font-size: 18px;}}@layer utilities {.text-lg {font-size: 20px;}}
Denne tomme deklarasjonen, @layer reset, base, components, utilities;, øverst i filen er ikke en formalitet – det er en eksplisitt fastsettelse av prioritetsrekkefølgen, før noen av lagene får noe reelt innhold. Takket være dette er lagrekkefølgen uavhengig av rekkefølgen på importer eller filer – components kan fysisk befinne seg i koden før utilities, og likevel vinner utilities alltid, fordi den rekkefølgen ble erklært fra starten.
Praktisk eksempel: reset, komponenter og verktøyklasser som aldri kjemper mot hverandre
I praksis er dette mønsteret – reset lavere enn komponenter, komponenter lavere enn verktøyklasser – akkurat slik lagsystemet i Tailwind har fungert siden versjon 3.1: en verktøyklasse skal alltid vinne over en komponentregel, uansett hvor mange klasser komponentens selektor har, fordi det er nettopp derfor verktøyklasser i det hele tatt eksisterer.
css
@layer reset, base, components, utilities;@layer reset {* {margin: 0;padding: 0;box-sizing: border-box;}}@layer base {body {font-family: system-ui, sans-serif;color: #1a1a1a;}}@layer components {.card {border: 1px solid #e0e0e0;border-radius: 12px;padding: 16px;}.card .card__header .card__title {font-size: 18px;font-weight: 600;}}@layer utilities {.text-lg {font-size: 20px !important;}.mt-4 {margin-top: 16px;}}
Legg merke til at !important på .text-lg i laget utilities er unødvendig her – laget sikrer allerede prioriteten, uten at du trenger å gripe til CSS-ens tyngste våpen. Dette illustrerer godt hvorfor cascade layers reelt reduserer antallet !important i koden: du slutter å trenge det som en omvei rundt spesifisitet, fordi du har et verktøy designet nøyaktig for denne oppgaven.
Den samme mekanismen løser et annet, like vanlig problem – et eksternt CSS-bibliotek hvis selektorer du ikke kontrollerer, og hvis prioritet du alltid vil ha lavere enn dine egne stiler, uten å øke spesifisiteten til dine egne regler:
css
@import url("some-ui-library.css") layer(vendor);@layer vendor, base, components, utilities;
Hele biblioteket havner i laget vendor, plassert helt først i rekkefølgen – så dine base, components og utilities overstyrer den alltid, uansett hvor aggressivt biblioteket bygger sine selektorer.
Fellen som bryter intuisjonen: stiler uten lag slår alle lag
Dette er den oftest oversette, og samtidig mest konsekvensrike detaljen ved hele mekanismen: en regel som ikke er i noe lag, vinner over en regel i et hvilket som helst lag – for vanlige, ikke-!important-deklarasjoner. Den «taper ikke fordi den er i et usynlig lag på slutten» – den vinner alltid, betingelsesløst, over enhver lag-regel, selv om sistnevnte er i laget erklært som høyeste prioritet.
css
@layer utilities {.text-lg {font-size: 20px;}}/* Denne regelen, selv om den ikke er i noe lag og ser ut som entilfeldig, enkeltstående override – VINNER over .text-lg ovenfor,uansett hvor i filen den befinner seg */.title {font-size: 16px;}
Denne oppførselen er bevisst – takket være den er det trygt å gradvis innføre cascade layers i et eksisterende, stort prosjekt: all «gammel» CSS som ennå ikke har havnet i noe lag, beholder som standard høyeste prioritet, så den begynner ikke plutselig å tape mot nylig lagt-til lag. Men den samme oppførselen er kilden til de mest forvirrende feilene i team som innfører @layer delvis: én enkelt regel som ved et uhell blir liggende utenfor et lag – en skrivefeil, en glemt import, en <style>-blokk injisert av et tredjepartsbibliotek – overstyrer hele det nøye designede prioritetssystemet, uten noen advarsel. Praktisk konsekvens: hvis du bestemmer deg for cascade layers, legg konsekvent alt i et eller annet lag, inkludert reset og grunnleggende stiler – ikke la noen regel bli liggende «utenfor», for det er den, ikke laget utilities, som vinner enhver konflikt.
Den andre fellen: !important snur lagrekkefølgen
Hvis du griper til !important inni et lag, snus prioritetsrekkefølgen mellom lagene. For vanlige deklarasjoner vinner laget deklarert senere. For deklarasjoner med !important – vinner laget deklarert tidligere.
css
@layer reset, components;@layer reset {.card { padding: 0 !important; }}@layer components {.card { padding: 16px !important; }}/* Reset (0px) vinner, fordi ved !important har laget deklarerttidligere fortrinnsrett – nøyaktig omvendt av regelensom gjelder uten !important */
Dette er ikke et innfall i spesifikasjonen – det er en konsekvent utvidelse av et prinsipp som allerede fantes for origin (brukerens !important-ark har alltid overstyrt sideforfatterens !important, motsatt av med vanlige deklarasjoner). Praktisk konklusjon: !important inni lagsystemet kan overraske selv noen som forstår den vanlige @layer-rekkefølgen godt – enda en god grunn til å behandle !important som noe du griper til bevisst og sjelden, ikke som standardmåten å vinne et sammenstøt på.
Fallgruver og gode praksiser
- Null regler utenfor lag, hvis systemet skal være forutsigbart. Som vist ovenfor slår selv én eneste regel uten
@layerhele lagsystemet for vanlige deklarasjoner – konsekvens er viktigere enn bekvemmeligheten ved å skrive til «bare én liten regel» i farten. - Spesifisitet gjelder fortsatt inni ett enkelt lag.
@layerfjerner spesifisitetskriger mellom lag, men inni ett lag avgjør to regler fortsatt en konflikt som alltid – fornuftig klassenavngivning og å unngå dyp nøsting av selektorer lønner seg fortsatt. - Erklær lagrekkefølgen eksplisitt, helt øverst, med en tom
@layer navn1, navn2, ...;. Dette fjerner avhengigheten av rekkefølgen filene importeres i – prioritetsrekkefølgen er fastsatt én gang, leselig, på ett sted, uansett i hvilken rekkefølge de enkelte CSS-filene faktisk lastes senere. - Nettleserstøtte er ikke lenger en grunn til forsiktighet. Cascade Layers oppnådde Baseline-status (bredt tilgjengelig) allerede i 2022 – Chrome 99, Firefox 97, Safari 15.4 – så de kan innføres uten
@supportsi ethvert nytt prosjekt.
Oppsummering
@layer konkurrerer ikke med spesifisitet – den virker på et helt annet trinn i kaskade-algoritmen, tidligere, og lar dermed én verktøyklasse bevisst og forutsigbart slå en selektor med tre nøstede klasser, uten å øke prioriteten og uten !important. Dette er et reelt arkitektonisk verktøy for å dele reset, grunnleggende stiler, komponenter og verktøyklasser inn i lag med eksplisitt erklært viktighetsrekkefølge – men to av dens regler bryter intuisjonen så kraftig at de er verdt å huske separat: en stil utenfor ethvert lag slår hvert eneste lag, og !important inni lag snur deres prioritetsrekkefølge. Å ignorere noen av disse to faktaene ender ikke med en feil i konsollen – det ender med CSS som oppfører seg annerledes enn den tilsynelatende logiske lagrekkefølgen i filen skulle tilsi.