KI skriver CSS som i 2015 – og slik endrer du det
Nedtrekksmenyen i toppteksten gjemmer seg under hero-seksjonen. Du markerer komponentfilen, skriver til assistenten «menyen gjemmer seg under heroen, fiks det», og får noen sekunder senere:
css
.menu {position: absolute;z-index: 9999 !important;}
Det fungerer ikke. Du skriver «den gjemmer seg fortsatt» – og får z-index: 99999, og i neste runde position: fixed med en top regnet ut i JavaScript. Samme uke ber du om å fikse et produktkort som brekker i et smalt sidepanel, og modellen legger til en tredje @media (max-width: 768px) som overstyrer bredden. Trekkspillet i FAQ-en? max-height: 1000px. En fullskjerms hero? height: 100vh. Horisontal scrolling på mobilen? body { overflow-x: hidden }.
Hvert av disse svarene er «riktig» i den forstand at noen en gang skrev det slik og at det fungerte. Men ingen av dem løser årsaken, og noen av dem – som du snart skal se – ødelegger noe annet et sted modellen aldri så på.
Hvorfor modellen griper til gamle mønstre
En språkmodell velger ikke den beste løsningen. Den velger den mest sannsynlige – den som oftest dukket opp i en lignende kontekst i dataene den har lært av. Og for CSS er disse dataene tydelig forskjøvet mot fortiden. Svar på Stack Overflow, veiledninger, kode fra repositorier, artikler fra 2010–2020 – en enorm mengde kode skrevet før cascade layers, container queries, :has() eller svh-enheter fantes. For hvert gode svar med @layer finnes det hundrevis av eldre svar med !important.
I tillegg kommer tiden. Funksjoner som i dag er stabile i alle nettlesere, kom ofte inn i dem i løpet av de siste to–tre årene. Modellen kjenner dem kanskje, men har langt færre eksempler på dem, og enda færre eksempler på riktig bruk i et ekte prosjekt. Den kjenner syntaksen, men ikke vanen.
Den tredje grunnen er den viktigste, fordi den ikke forsvinner med nyere modeller: modellen ser ikke siden. Den får filen til menykomponenten, og et lagproblem ligger nesten aldri i menyen. Det ligger i en forfar som lager en ny stablingskontekst – via position: sticky, transform eller opacity – og den forfaren står i en annen fil, en annen komponent, noen ganger i CSS-en til et bibliotek. En modell som bare ser .menu, har én spak tilgjengelig: tallet i z-index. Så den øker det. Den gjør akkurat det et menneske uten devtools ville gjort.
Mønstre det er verdt å kjenne igjen med en gang
Noen svar dukker opp så ofte at det lønner seg å se dem som et signal om at «modellen fikset symptomet, ikke årsaken»:
!important, eller enz-indexmed flere nitall. Betyr nesten alltid en spesifisitetskonflikt eller en stablingskontekst et sted høyere opp i treet. Spesifisitet løses med@layer– jeg skrev om det i artikkelen om cascade layers – og lag på Z-aksen løses ved å finne forfaren som lager konteksten.- En media query for en komponent. Et kort som skal endre seg ut fra hvor det står, ikke ut fra skjermbredden, trenger container queries – det forklarte jeg i artikkelen om container queries med akkurat et slikt kort i et sidepanel.
max-height: 1000pxi et trekkspill, og JavaScript som venter påtransitionendfør det skjuler et element. I dag kan du animereheight: autoogdisplay: nonei ren CSS.height: 100vhpå en fullskjermseksjon. På en telefon er det høyere enn den synlige skjermen, fordivhmåles med nettleserens verktøylinjer skjult.overflow-x: hiddenpåbody. Skjuler den horisontale scrollingen, men gjør i det stilleoverflow-yom tilautoog slår av alleposition: stickypå siden.- En IntersectionObserver, en
scroll-lytter eller et bibliotek for effekter CSS allerede kan selv: animasjoner knyttet til scrolling (beskrevet i artikkelen om scroll-driven animations), å oppdage en festet topptekst, masonry-layout. - Fire kopier av fargepaletten for dark mode – i
:root, i@media (prefers-color-scheme)og i to[data-theme]-selektorer.
Hvert av disse punktene skriver jeg om i et eget innlegg i ukene som kommer. Det viktigste her er noe annet: alle har en felles nevner. Modellen velger en løsning som får plass i filen du viste den, og som var den vanligste for noen år siden.
En regelfil: gi modellen konteksten den mangler
Den første måten å endre disse vanene på er enkel og gir overraskende mye: en regelfil for prosjektet som assistenten leser inn ved hver oppgave. I Claude Code er det CLAUDE.md, i Cursor filer i .cursor/rules, i GitHub Copilot .github/copilot-instructions.md, og stadig flere verktøy leser også en felles AGENTS.md. Navnet varierer, mekanismen er den samme: tekst som havner i konteksten til modellen før den ser forespørselen din.
Det virker fordi det forskyver sannsynlighetsfordelingen. Uten regler er det mest sannsynlige svaret på «menyen gjemmer seg under heroen» z-index: 9999. Med regelen «ikke øk z-index før du har pekt ut forfaren som lager stablingskonteksten» blir det mest sannsynlige svaret å lete etter forfaren. Modellen trenger ikke «vite mer»; det holder at den vet hva du forventer.
Slik ser en CSS-seksjon ut som det er verdt å starte med:
markdown
## CSSMål: nettlesere fra de siste 2 årene (Baseline). Ikke legg til fallbackerfor eldre uten å spørre.Arkitektur- Rekkefølge på lag: @layer reset, base, components, utilities.Hver regel hører til et lag.- Ingen !important utenfor laget utilities.- Begrens komponentstiler med @scope, ikke med lange selektorer.Layout- Komponenter reagerer på beholderen (container queries), ikke viewporten.Media queries bare for layouten til hele siden.- Ingen fast height på elementer med innhold. min-height, grid, subgrid.- Skjermhøyde: svh (standard) eller dvh. Aldri 100vh. Aldri 100vw forfull bredde.- Kutte innhold: overflow: clip, ikke hidden. Aldri overflow-x: hiddenpå body.Lag- z-index bare fra --z-*-variabler. Før du endrer z-index: pek ut forfarensom lager stablingskonteksten, og forklar hvorfor.- Roten til en komponent med lag inni: isolation: isolate.- Modaler, menyer og verktøytips: <dialog> eller popover (top layer), ikke z-index.Farger og tema- Bare tokens fra tokens.css. Ikke innfør nye hex-verdier.- Dark mode: light-dark() + color-scheme. Ingen duplisert palett.Animasjoner- Animer transform og opacity. Inn/ut: @starting-style,transition-behavior: allow-discrete. Alltid prefers-reduced-motion.Før du fikser en feil i layout, lag eller scrolling: oppgi årsaken først(hvilket element, hvilken egenskap), deretter fiksen.
Noen ting i denne filen er bevisste. Reglene er konkrete – «aldri overflow-x: hidden på body», ikke «skriv moderne CSS», for et generelt slagord tolker modellen på sin egen måte. Forbudene har erstatninger – et forbud mot !important alene får modellen til å øke spesifisiteten i selektoren i stedet; forbud pluss @layer gir den riktig verktøy. Det siste avsnittet tvinger fram en rekkefølge i tenkningen: først årsaken, så koden. Denne ene setningen endrer svarene mer enn alle de andre, fordi den tvinger modellen til å si hva den ikke vet – og da ser du når den gjetter.
Tilpass filen til prosjektet ditt. Støtter du eldre nettlesere, skriv hvilke. Bruker du Tailwind, ser reglene om lag annerledes ut. Filen skal beskrive ditt prosjekt, ikke et ideelt et.
Fellen: modellen fikser symptomet fordi den ikke ser siden
En regelfil endrer vaner, men løser ikke det tredje problemet fra starten: modellen ser fortsatt kode, ikke siden. Regelen «pek ut forfaren som lager stablingskonteksten» hjelper lite hvis den forfaren ikke finnes i noen fil modellen har lest. Da får du høflig gjetting: «en av foreldrene har sannsynligvis en transform».
Det som mangler, er det du ser i devtools: beregnede stiler og treet av forfedre. Den enkleste måten å gi dem videre på er et kort skript i konsollen og å lime resultatet inn i samtalen:
javascript
// Marker det problematiske elementet i fanen Elements (i konsollen: $0)let el = $0const out = []while ((el = el.parentElement)) {const s = getComputedStyle(el)out.push({el: el.tagName.toLowerCase() + (el.className ? "." + el.className.split(" ").join(".") : ""),position: s.position,zIndex: s.zIndex,overflow: s.overflowX + " / " + s.overflowY,transform: s.transform !== "none",opacity: s.opacity,})}copy(JSON.stringify(out, null, 2)) // resultatet havner på utklippstavlen
Du limer det inn sammen med forespørselen, og plutselig har modellen de samme dataene en erfaren frontendutvikler ville hatt: den ser at tre nivåer opp står en header med position: sticky uten z-index, og foreslår en z-index på toppteksten i stedet for menyen. Svaret slutter å være gjetting.
Den andre, mer praktiske måten er å gi assistenten direkte tilgang til nettleseren. Agenter som Claude Code kan styre en nettleser via en utvidelse eller en MCP-server – Playwright, Chrome DevTools – og selv lese beregnede stiler, gå gjennom DOM-treet og ta et skjermbilde før og etter fiksen. Det endrer arbeidet: i stedet for «skriv en fiks» kan du be om «finn ut hvorfor menyen gjemmer seg under heroen, vis meg årsaken, fiks det og ta et skjermbilde». En modell som kan sjekke resultatet, slutter å levere kode som «burde fungere».
Spør om årsaken, ikke om koden
Den enkleste endringen krever verken fil eller verktøy – bare en annen forespørsel. Sammenlign:
markdown
Dårlig:Menyen gjemmer seg under heroen. Fiks det.Bra:Menyen (.site-nav__dropdown) gjemmer seg under seksjonen .hero.Før du endrer noe: hvilket element lager stablingskontekstenmenyen ligger i, og hvorfor dekker heroen den?Ikke øk z-index på selve menyen.Under er de beregnede stilene til forfedrene til menyen:[innlimt JSON]
Den første forespørselen gir modellen bare én vei – den korteste, altså den mest sannsynlige. Den andre stenger den veien eksplisitt, gir data og ber om en forklaring. God praksis fra kodegjennomgang virker her akkurat som med kode fra en juniorutvikler: hvis forfatteren av en fiks ikke kan si hvorfor den virker, virker den sannsynligvis ikke av grunnen de tror – eller den virker ved et tilfelle og slutter ved neste endring.
Den andre fellen: ny syntaks som ikke finnes
Når du først har overbevist modellen om å skrive moderne CSS, dukker det motsatte problemet opp. Funksjoner som først nå kommer inn i nettleserne, endrer syntaks under standardiseringen – og modellen kan med full overbevisning skrive en variant som sto i et spesifikasjonsutkast for to år siden og aldri kom inn i noen nettleser. Eller riktig syntaks for en funksjon som i dag bare virker i én motor, uten et ord om fallback.
Slik kode gir ingen feil: en ukjent CSS-egenskap ignoreres i det stille. Sjekk derfor hver funksjon du ikke kjenner fra egen erfaring på MDN eller caniuse før den havner i repositoriet – og skriv i regelfilen hvilke nye funksjoner som er tillatt i prosjektet, og hvilke som krever @supports.
Feller og god praksis
!importantogz-index: 9999fra KI er et signal, ikke en løsning. De betyr nesten alltid en spesifisitetskonflikt eller en stablingskontekst et sted modellen ikke så.- En regelfil endrer vanene til modellen.
CLAUDE.md,.cursor/rules,copilot-instructions.md– konkrete regler med erstatninger virker, generelle slagord gjør det ikke. - Tving fram rekkefølgen: årsak, så kode. Én setning i reglene eller i forespørselen endrer mer enn en liste med forbud.
- Gi modellen det du ser i devtools. Beregnede stiler for forfedrene limt inn i samtalen, eller nettlesertilgang via MCP, gjør gjetting om til diagnose.
- Sjekk nye funksjoner ved kilden. Modellen kan kjenne utdatert syntaks eller hoppe over manglende støtte. MDN og caniuse før commit.
- Gå gjennom KI-kode som kode fra et nytt teammedlem. Kommer en fiks uten forklaring, be om den før du godtar den.
Oppsummering
En KI-assistent skriver CSS fra for ti år siden av tre grunner: den har lært mest av eldre kode, den kjenner nye funksjoner fra langt færre eksempler, og fremfor alt ser den ikke siden – bare filen du viste den, så den fikser symptomet med den eneste spaken den har for hånden. En regelfil flytter vanene dens mot moderne CSS, en forespørsel om «årsak først» tvinger fram diagnose i stedet for gjetting, og beregnede stiler eller nettlesertilgang gir den det den mangler mest: et bilde av den ekte siden. Ingen av disse verktøyene fritar deg fra å forstå CSS – tvert imot: jo bedre du vet hvorfor z-index: 9999 ikke hjelper, desto raskere ser du når modellen prøver å selge deg det. I de neste innleggene går jeg gjennom problemene over, ett etter ett.