KI schreibt CSS wie 2015 – und wie du das änderst
Das Dropdown-Menü im Header verschwindet unter dem Hero-Abschnitt. Du markierst die Komponentendatei, schreibst dem Assistenten „das Menü verschwindet unter dem Hero, behebe das“ und bekommst ein paar Sekunden später:
css
.menu {position: absolute;z-index: 9999 !important;}
Funktioniert nicht. Du schreibst „verschwindet immer noch“ – und bekommst z-index: 99999, in der nächsten Runde position: fixed mit einem per JavaScript berechneten top. In derselben Woche bittest du darum, eine Produktkarte zu reparieren, die in einer schmalen Sidebar zerbricht, und das Modell ergänzt eine dritte @media (max-width: 768px), die die Breite überschreibt. Das FAQ-Akkordeon? max-height: 1000px. Ein Vollbild-Hero? height: 100vh. Horizontales Scrollen auf dem Smartphone? body { overflow-x: hidden }.
Jede dieser Antworten ist „richtig“ in dem Sinne, dass irgendwer es einmal so geschrieben hat und es funktioniert hat. Aber keine behebt die Ursache, und manche – wie du gleich sehen wirst – machen etwas anderes kaputt, an einer Stelle, die sich das Modell nie angesehen hat.
Warum das Modell zu alten Mustern greift
Ein Sprachmodell wählt nicht die beste Lösung. Es wählt die wahrscheinlichste – die, die in einem ähnlichen Kontext in seinen Trainingsdaten am häufigsten vorkam. Und bei CSS sind diese Daten stark in Richtung Vergangenheit verschoben. Antworten auf Stack Overflow, Tutorials, Code aus Repositories, Artikel aus den Jahren 2010–2020 – eine riesige Menge Code, geschrieben, bevor es Cascade Layers, Container Queries, :has() oder svh-Einheiten gab. Auf eine gute Antwort mit @layer kommen Hunderte ältere Antworten mit !important.
Dazu kommt die Zeit. Funktionen, die heute in allen Browsern stabil sind, sind dort oft erst in den letzten zwei, drei Jahren angekommen. Das Modell kennt sie vielleicht, hat aber viel weniger Beispiele dafür und noch weniger Beispiele für richtigen Einsatz in einem echten Projekt. Es kennt die Syntax, aber nicht die Gewohnheit.
Der dritte Grund ist der wichtigste, weil er mit neueren Modellen nicht verschwindet: Das Modell sieht die Seite nicht. Es bekommt die Datei der Menükomponente, und ein Ebenenproblem liegt fast nie im Menü. Es liegt in einem Vorfahren, der einen neuen Stapelkontext erzeugt – durch position: sticky, transform oder opacity –, und dieser Vorfahre steht in einer anderen Datei, einer anderen Komponente, manchmal im CSS einer Bibliothek. Ein Modell, das nur .menu sieht, hat einen einzigen Hebel: die Zahl in z-index. Also erhöht es sie. Es tut genau das, was ein Mensch ohne DevTools tun würde.
Muster, die man sofort erkennen sollte
Einige Antworten kommen so oft vor, dass man sie als Signal „das Modell hat das Symptom behoben, nicht die Ursache“ behandeln sollte:
!importantoder einz-indexmit mehreren Neunen. Bedeutet fast immer einen Spezifitätskonflikt oder einen Stapelkontext irgendwo weiter oben im Baum. Spezifität lösen@layer– darüber habe ich im Artikel über Cascade Layers geschrieben –, Ebenen auf der Z-Achse löst man, indem man den Vorfahren findet, der den Kontext erzeugt.- Eine Media Query für eine Komponente. Eine Karte, die sich je nach Platz und nicht nach Bildschirmbreite ändern soll, braucht Container Queries – das habe ich im Artikel über Container Queries an genau so einer Karte in einer Sidebar erklärt.
max-height: 1000pxim Akkordeon und JavaScript, das auftransitionendwartet, bevor es ein Element ausblendet. Heute lassen sichheight: autounddisplay: noneallein mit CSS animieren.height: 100vhfür einen Vollbild-Abschnitt. Auf dem Smartphone ist das höher als der sichtbare Bildschirm, weilvhbei ausgeblendeten Browserleisten gemessen wird.overflow-x: hiddenaufbody. Versteckt das horizontale Scrollen, macht aber stillschweigendoverflow-yzuautound schaltet jedesposition: stickyauf der Seite ab.- Ein IntersectionObserver, ein
scroll-Listener oder eine Bibliothek für Effekte, die CSS schon selbst kann: ans Scrollen gekoppelte Animationen (beschrieben im Artikel über Scroll-driven Animations), das Erkennen eines klebenden Headers, Masonry-Layout. - Vier Kopien der Farbpalette für Dark Mode – in
:root, in@media (prefers-color-scheme)und in zwei[data-theme]-Selektoren.
Jeden dieser Punkte beschreibe ich in den kommenden Wochen in einem eigenen Beitrag. Wichtiger ist hier etwas anderes: Alle haben einen gemeinsamen Nenner. Das Modell wählt eine Lösung, die in die Datei passt, die du ihm gezeigt hast, und die vor ein paar Jahren die häufigste war.
Eine Regeldatei: Gib dem Modell den Kontext, der ihm fehlt
Der erste Weg, diese Gewohnheiten zu ändern, ist einfach und bringt überraschend viel: eine Regeldatei für das Projekt, die der Assistent bei jeder Aufgabe einliest. In Claude Code ist das CLAUDE.md, in Cursor sind es Dateien in .cursor/rules, in GitHub Copilot .github/copilot-instructions.md, und immer mehr Werkzeuge lesen auch eine gemeinsame AGENTS.md. Der Name ist verschieden, der Mechanismus derselbe: Text, der im Kontext des Modells landet, bevor es deine Anfrage sieht.
Das funktioniert, weil es die Wahrscheinlichkeitsverteilung verschiebt. Ohne Regeln ist die wahrscheinlichste Antwort auf „das Menü verschwindet unter dem Hero“ z-index: 9999. Mit der Regel „erhöhe z-index erst, wenn du den Vorfahren benannt hast, der den Stapelkontext erzeugt“ wird die Suche nach dem Vorfahren zur wahrscheinlichsten Antwort. Das Modell muss nicht „mehr wissen“; es genügt, dass es weiß, was du erwartest.
So sieht ein CSS-Abschnitt aus, mit dem man anfangen kann:
markdown
## CSSZiel: Browser der letzten 2 Jahre (Baseline). Keine Fallbacks für ältereohne Rückfrage.Architektur- Layer-Reihenfolge: @layer reset, base, components, utilities.Jede Regel gehört zu einem Layer.- Kein !important außerhalb des Layers utilities.- Komponenten-Styles mit @scope begrenzen, nicht mit langen Selektoren.Layout- Komponenten reagieren auf ihren Container (Container Queries), nichtauf den Viewport. Media Queries nur für das Layout der ganzen Seite.- Keine feste height für Elemente mit Inhalt. min-height, Grid, Subgrid.- Bildschirmhöhe: svh (Standard) oder dvh. Nie 100vh. Nie 100vw fürvolle Breite.- Inhalt abschneiden: overflow: clip, nicht hidden. Nie overflow-x: hiddenauf body.Ebenen- z-index nur aus --z-*-Variablen. Vor einer Änderung von z-index denVorfahren nennen, der den Stapelkontext erzeugt, und begründen.- Wurzel einer Komponente mit Ebenen im Inneren: isolation: isolate.- Modals, Menüs und Tooltips: <dialog> oder popover (Top Layer), nicht z-index.Farben und Theme- Nur Tokens aus tokens.css. Keine neuen Hex-Werte einführen.- Dark Mode: light-dark() + color-scheme. Keine duplizierte Palette.Animationen- transform und opacity animieren. Ein-/Ausblenden: @starting-style,transition-behavior: allow-discrete. Immer prefers-reduced-motion.Bevor du einen Fehler in Layout, Ebenen oder Scrollen behebst: zuerst dieUrsache nennen (welches Element, welche Eigenschaft), dann den Fix.
Einiges in dieser Datei ist Absicht. Die Regeln sind konkret – „nie overflow-x: hidden auf body“, nicht „schreib modernes CSS“, denn ein allgemeines Schlagwort legt das Modell auf seine Weise aus. Verbote haben Ersatz – ein reines Verbot von !important führt dazu, dass das Modell die Spezifität des Selektors erhöht; Verbot plus @layer gibt ihm das richtige Werkzeug. Der letzte Absatz erzwingt eine Reihenfolge des Denkens: erst die Ursache, dann Code. Dieser eine Satz verändert die Antworten mehr als alle anderen, weil er das Modell zwingt zu sagen, was es nicht weiß – und dann sieht man, wann es rät.
Passe die Datei an dein Projekt an. Unterstützt du ältere Browser, schreib welche. Nutzt du Tailwind, sehen die Regeln zu Layern anders aus. Die Datei soll dein Projekt beschreiben, nicht ein ideales.
Die Falle: Das Modell behebt das Symptom, weil es die Seite nicht sieht
Eine Regeldatei ändert Gewohnheiten, löst aber das dritte Problem vom Anfang nicht: Das Modell sieht weiterhin Code, nicht die Seite. Die Regel „nenne den Vorfahren, der den Stapelkontext erzeugt“ bringt wenig, wenn dieser Vorfahre in keiner Datei steht, die das Modell gelesen hat. Dann bekommst du höfliches Raten: „Wahrscheinlich hat eines der Elternelemente ein transform.“
Das fehlende Stück ist das, was du in den DevTools siehst: berechnete Styles und den Baum der Vorfahren. Am einfachsten übergibst du sie mit einem kurzen Skript in der Konsole und fügst das Ergebnis ins Gespräch ein:
javascript
// Markiere das problematische Element im Tab Elements (in der Konsole: $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)) // das Ergebnis landet in der Zwischenablage
Du fügst das zusammen mit der Anfrage ein, und plötzlich hat das Modell dieselben Daten, die ein erfahrener Frontend-Entwickler hätte: Es sieht, dass drei Ebenen höher ein header mit position: sticky ohne z-index liegt, und schlägt einen z-index am Header vor statt am Menü. Die Antwort hört auf, geraten zu sein.
Der zweite, bequemere Weg ist, dem Assistenten direkten Zugriff auf den Browser zu geben. Agenten wie Claude Code können einen Browser über eine Erweiterung oder einen MCP-Server – Playwright, Chrome DevTools – steuern und selbst berechnete Styles auslesen, durch den DOM-Baum gehen und vor und nach dem Fix einen Screenshot machen. Das verändert die Art der Arbeit: Statt „schreib einen Fix“ kannst du bitten „finde heraus, warum das Menü unter dem Hero verschwindet, zeig mir die Ursache, behebe es dann und mach einen Screenshot“. Ein Modell, das das Ergebnis prüfen kann, liefert keinen Code mehr ab, der „funktionieren sollte“.
Frag nach der Ursache, nicht nach dem Code
Die einfachste Änderung braucht weder Datei noch Werkzeug – nur eine andere Anfrage. Vergleiche:
markdown
Schlecht:Das Menü verschwindet unter dem Hero. Behebe das.Gut:Das Menü (.site-nav__dropdown) verschwindet unter dem Abschnitt .hero.Bevor du etwas änderst: Welches Element erzeugt den Stapelkontext,in dem das Menü liegt, und warum verdeckt der Hero es?Erhöhe nicht den z-index des Menüs selbst.Unten die berechneten Styles der Vorfahren des Menüs:[eingefügtes JSON]
Die erste Anfrage lässt dem Modell nur einen Weg – den kürzesten, also den wahrscheinlichsten. Die zweite verschließt diesen Weg ausdrücklich, liefert Daten und bittet um eine Erklärung. Gute Praxis aus dem Code-Review gilt hier genauso wie bei Code von einem Junior: Kann der Autor eines Fixes nicht sagen, warum er funktioniert, funktioniert er vermutlich nicht aus dem Grund, den er glaubt – oder er funktioniert zufällig und hört bei der nächsten Änderung auf.
Die zweite Falle: neue Syntax, die es nicht gibt
Hast du das Modell erst einmal zu modernem CSS überredet, taucht das umgekehrte Problem auf. Funktionen, die gerade erst in die Browser kommen, ändern während der Standardisierung ihre Syntax – und das Modell schreibt mit voller Überzeugung eine Variante, die vor zwei Jahren in einem Spezifikationsentwurf stand und nie in einem Browser gelandet ist. Oder die richtige Syntax einer Funktion, die heute nur in einer Engine funktioniert, ohne ein Wort über einen Fallback.
Solcher Code meldet keine Fehler: Eine unbekannte CSS-Eigenschaft wird stillschweigend ignoriert. Prüfe deshalb jede Funktion, die du nicht aus eigener Erfahrung kennst, auf MDN oder caniuse, bevor sie im Repository landet – und schreib in die Regeldatei, welche neuen Funktionen im Projekt erlaubt sind und welche @supports brauchen.
Fallen und Best Practices
!importantundz-index: 9999von der KI sind ein Signal, keine Lösung. Sie bedeuten fast immer einen Spezifitätskonflikt oder einen Stapelkontext an einer Stelle, die das Modell nicht gesehen hat.- Eine Regeldatei ändert die Gewohnheiten des Modells.
CLAUDE.md,.cursor/rules,copilot-instructions.md– konkrete Regeln mit Ersatz wirken, allgemeine Schlagworte nicht. - Erzwinge die Reihenfolge: Ursache, dann Code. Ein Satz in den Regeln oder in der Anfrage ändert mehr als eine Liste von Verboten.
- Gib dem Modell, was du in den DevTools siehst. Berechnete Styles der Vorfahren im Gespräch oder Browserzugriff per MCP machen aus Raten eine Diagnose.
- Prüfe neue Funktionen an der Quelle. Das Modell kennt womöglich veraltete Syntax oder übergeht fehlende Unterstützung. MDN und caniuse vor dem Commit.
- Prüfe KI-Code wie Code von einer neuen Person im Team. Kommt ein Fix ohne Erklärung, frag danach, bevor du ihn übernimmst.
Fazit
Ein KI-Assistent schreibt CSS von vor zehn Jahren aus drei Gründen: Er hat überwiegend an älterem Code gelernt, kennt neue Funktionen aus viel weniger Beispielen, und vor allem sieht er die Seite nicht – nur die Datei, die du ihm gezeigt hast, also behebt er das Symptom mit dem einzigen Hebel, den er zur Hand hat. Eine Regeldatei stellt seine Gewohnheiten auf modernes CSS um, die Anfrage „erst die Ursache“ zwingt zur Diagnose statt zum Raten, und berechnete Styles oder Browserzugriff geben ihm, was ihm am meisten fehlt: ein Bild der echten Seite. Keines dieser Werkzeuge entbindet davon, CSS zu verstehen – im Gegenteil: Je besser du weißt, warum z-index: 9999 nicht hilft, desto schneller merkst du, wann das Modell es dir verkaufen will. In den nächsten Beiträgen gehe ich die oben genannten Probleme nacheinander durch.