CSS: Grundlagen
CSS-Selektoren: Übersicht und Spezifität
Ein CSS-Selektor legt fest, welche Elemente eine Regel treffen. Fünf Grundtypen, vier Kombinatoren, Pseudoklassen und Pseudoelemente decken jeden Fall ab. Bei Konflikten entscheidet die Spezifität in der Schreibweise 0-1-0.
Selektoren sind die Schnittstelle zwischen deinem Markup und deinem Stylesheet. Je genauer das HTML benannt ist, desto kürzer fallen sie aus. Eine Seite mit sauberer semantischer Struktur kommt mit Typselektoren und wenigen Klassen aus. Eine Seite aus verschachtelten div-Containern braucht lange Ketten, die bei jeder Umstellung des Markups nachgezogen werden müssen.
Die zweite Hälfte des Themas ist die Spezifität. Sie entscheidet, welche von zwei widersprechenden Regeln gewinnt. Wer sie im Kopf ausrechnen kann, braucht !important nur in seltenen Ausnahmen. Dieser Text zeigt jeden Selektortyp mit Beispiel und rechnet die Spezifität an konkreten Fällen vor.
Das Wichtigste in Kürze
- Fünf Grundtypen decken die Auswahl ab: Typ (
p), Klasse (.karte), ID (#nav), Universal (*) und Attribut ([type="email"]). Ein Komma gruppiert mehrere Selektoren zu einer Regel. - Vier Kombinatoren verbinden Selektoren miteinander: das Leerzeichen (Nachfahre),
>(Kind),+(nächstes Geschwister) und~(alle folgenden Geschwister). - Die Spezifität hat drei Stellen in der Form ID-Klasse-Typ.
#nav a.aktivergibt 1-1-1 und schlägt damit jede Regel, die bei 0-2-0 liegt. :where()hat immer die Spezifität 0-0-0.:is()und:not()übernehmen die Spezifität ihres stärksten Arguments.:focus-visiblezeigt den Fokusring bei Tastaturbedienung und lässt ihn beim Mausklick weg. Ein pauschalesoutline: noneverstößt gegen WCAG 2.4.7 Fokus sichtbar (Stufe AA).- Verschachtelte Selektoren gehören seit 2023 zum Standard. Stand September 2026 funktionieren sie in allen aktuellen Browsern ohne Präprozessor.
Welche CSS-Selektoren gibt es? Die Liste im Überblick
Die Übersicht enthält jeden Selektortyp des Standards. Die Abschnitte danach zeigen zu jedem Eintrag lauffähigen Code.
| Selektor | Beispiel | Trifft |
|---|---|---|
| Typ | p |
jedes p-Element im Dokument |
| Klasse | .karte |
jedes Element mit class="karte" |
| ID | #nav |
das Element mit id="nav" |
| Universal | * |
jedes Element |
| Attribut | [type="email"] |
Elemente mit diesem Attributwert |
| Gruppierung | h1, h2, h3 |
alle drei Elementtypen |
| Nachfahre | nav a |
jedes a irgendwo innerhalb von nav |
| Kind | ul > li |
nur die direkten Kinder |
| Nachbar | h2 + p |
das p direkt nach einer h2 |
| Geschwister | h2 ~ p |
alle p nach h2 im selben Elternelement |
| Pseudoklasse | a:hover |
ein Element in einem Zustand |
| Pseudoelement | li::marker |
einen erzeugten Teil eines Elements |
Typ, Klasse und ID
/* Typselektor: alle Absätze */
p { line-height: 1.6; }
/* Klassenselektor: beliebig oft im Dokument */
.karte { border: 1px solid var(--rahmen); }
/* ID-Selektor: die ID darf je Dokument nur einmal vergeben werden */
#hauptnavigation { position: sticky; }
Klassen sind das Arbeitspferd, weil sie mehrfach vorkommen dürfen und eine mittlere Spezifität haben. IDs bleiben im HTML nützlich als Sprungziel und als Anker für aria-labelledby oder for. Als Selektor bringen sie eine Spezifität mit, die später kaum zu überschreiben ist. Ohne Trennzeichen verbinden sich zwei Selektoren zu einer Bedingung: a.knopf trifft nur a-Elemente, die zusätzlich die Klasse knopf haben.
Universalselektor und Attributselektoren
Der Universalselektor * trifft jedes Element und hat die Spezifität 0-0-0. Sein häufigster Einsatz steht in fast jedem Stylesheet ganz oben.
*, *::before, *::after { box-sizing: border-box; }
Der Attributselektor (englisch attribute selector) prüft das Vorhandensein eines Attributs oder seinen Wert. Er ist der direkteste Weg, aus dem Stylesheet auf den Zustand eines Bedienelements zu reagieren.
[disabled] { opacity: .6; } /* Attribut vorhanden */
input[type="email"] { font-variant: tabular-nums; } /* exakter Wert */
a[href^="https://"] { } /* Wert beginnt mit */
a[href$=".pdf"]::after { content: " (PDF)"; } /* Wert endet auf */
a[href*="/kontakt"] { } /* Wert enthält */
[class~="warnung"] { } /* Wert in einer Liste mit Leerzeichen */
[lang|="de"] { } /* Sprachcode vor dem Bindestrich */
a[href$=".PDF" i] { } /* Vergleich ohne Groß- und Kleinschreibung */
Besonders wertvoll ist der Zugriff auf ARIA-Zustände. Eine Regel wie [aria-expanded="true"] .pfeil { rotate: 180deg; } gestaltet genau den Zustand, den auch die Screenreader-Ausgabe meldet. Optik und Barrierefreiheits-Schnittstelle bleiben so verbunden. Welche Attribute dafür in Frage kommen, steht in der ersten Regel von ARIA.
Gruppierung mit dem Komma
h1, h2, h3 { text-wrap: balance; }
Ein Komma fasst mehrere Selektoren zu einer Regel zusammen. Diese Liste ist streng: Ein einziger ungültiger Teil macht die gesamte Regel ungültig, und der Browser verwirft alle Deklarationen. Die Funktion :is() verhält sich toleranter und ignoriert allein den unbekannten Teil.
CSS-Selektoren kombinieren: Beispiele für die vier Kombinatoren
Kombinatoren beschreiben die Lage zweier Elemente im Dokumentbaum zueinander. Sie selbst erhöhen die Spezifität nicht.
nav a { text-decoration: none; } /* Nachfahre: beliebig tief verschachtelt */
.menue > li { display: flex; } /* Kind: genau eine Ebene tiefer */
h2 + p { margin-block-start: .25rem; } /* Nachbar: das direkt folgende Geschwister */
h2 ~ p { color: var(--text-leise); } /* Geschwister: alle folgenden Absätze */
Die beiden Geschwister-Kombinatoren (englisch sibling combinators) arbeiten ausschließlich vorwärts im Dokument. + verlangt, dass das Element unmittelbar folgt. ~ trifft jedes folgende Geschwister ohne Rücksicht auf den Abstand. Rückwärts gibt es keinen Kombinator. Diese Lücke füllt seit 2023 die Pseudoklasse :has().
Ein praktischer Fall ist die Fehlermeldung neben einem Formularfeld. Sie zeigt, wie ein Zustand und ein Kombinator zusammenspielen.
/* Die Meldung erscheint erst nach einer Eingabe */
input:user-invalid + .feldfehler { display: block; }
input:user-invalid { border-color: var(--fehler); }
:user-invalid greift erst, wenn die Person das Feld verlassen oder das Formular abgeschickt hat. Die Meldung selbst gehört ins Markup und wird über aria-describedby
verknüpft. Wie diese Verbindung aufgebaut wird, steht unter
Formular-Semantik.
Pseudoklassen: Zustände, Positionen und Beziehungen
Zustände am Bedienelement
.knopf:hover { background: var(--flaeche-hover); }
.knopf:focus-visible { outline: 3px solid var(--fokus); outline-offset: 2px; }
.feldgruppe:focus-within { border-color: var(--fokus); }
:hover gilt für den Mauszeiger. :focus gilt, sobald das Element den Fokus hat. :focus-visible überlässt dem Browser die Entscheidung, ob der Fokus sichtbar angezeigt werden soll. Bei Tastatur und Sprachsteuerung fällt diese Entscheidung positiv aus. Beim Mausklick auf einen Button bleibt der Ring aus. :focus-within trifft jedes Elternelement, in dem gerade etwas den Fokus hat. Damit lassen sich Feldgruppen und Karten hervorheben, ohne dass JavaScript den Zustand nachhalten muss.
Positionen in der Geschwisterfolge
tbody tr:nth-child(even) { background: var(--zebra); }
li:first-child { margin-block-start: 0; }
p:nth-of-type(2) { font-size: 1.125rem; }
li:nth-child(3n + 1) { grid-column: 1; }
li:nth-child(2 of .aktiv) { outline: 2px dashed; }
:nth-child() zählt alle Geschwister, :nth-of-type() nur die des gleichen Elementtyps. Die Schreibweise of .aktiv schränkt die Zählung auf passende Elemente ein und funktioniert Stand September 2026 in allen aktuellen Browsern. Der klassische Einsatz sind Zebrastreifen in semantisch aufgebauten Tabellen. Sie bleiben dabei reine Lesehilfe. Sobald eine Farbe eine Information vermittelt, verlangt 1.4.1 Benutzung von Farbe ein zweites Merkmal.
Beziehungen mit :is(), :where(), :not() und :has()
:is(h1, h2, h3) > code { font-size: .9em; }
:where(.prosa) a { text-decoration: underline; }
li:not(.aktiv) { opacity: .7; }
label:has(input:checked) { font-weight: 600; }
.karte:has(> img) { padding-block-start: 0; }
:is() kürzt lange Aufzählungen ab und übernimmt die Spezifität seines stärksten Arguments. :where() funktioniert gleich, zählt aber immer als 0-0-0. Das macht es zum richtigen Werkzeug für Grundstile, die eine einzelne Klasse später überschreiben soll. :has() prüft, ob ein Element bestimmte Nachfahren oder Geschwister enthält, und wirkt damit als Elternselektor. Seit Firefox 121 im Dezember 2023 unterstützen alle aktuellen Browser die Funktion.
Pseudoelemente: ::before, ::after, ::marker und ::placeholder
Pseudoelemente gestalten einen Teil eines Elements, der im HTML nicht als eigenes Element steht. Seit CSS3 stehen zwei Doppelpunkte davor. Für die vier ältesten Pseudoelemente verstehen Browser weiterhin die Schreibweise mit einem Doppelpunkt.
.zitat::before { content: "„"; }
a[href$=".pdf"]::after { content: " (PDF)"; }
li::marker { color: var(--akzent); font-variant-numeric: tabular-nums; }
input::placeholder { color: var(--text-leise); }
::before und ::after erscheinen erst, wenn content gesetzt ist. Sie liegen innerhalb des Elements als erstes und letztes Kind. ::marker gestaltet den Aufzählungspunkt oder die Nummer einer richtig ausgezeichneten Liste und nimmt Farbe, Schriftschnitt und content an. ::placeholder gestaltet den Platzhalter in Eingabefeldern und bleibt dabei lesbarer Text mit den üblichen Kontrastanforderungen.
CSS-Selektoren und Spezifität: so rechnest du sie aus
Die Spezifität ist eine Zahl mit drei Stellen. Die erste Stelle zählt ID-Selektoren, die zweite Klassen, Attributselektoren und Pseudoklassen, die dritte Typselektoren und Pseudoelemente. Der Universalselektor und alle Kombinatoren zählen null.
| Selektor | ID | Klasse | Typ | Ergebnis |
|---|---|---|---|---|
* |
0 | 0 | 0 | 0-0-0 |
li |
0 | 0 | 1 | 0-0-1 |
ul > li a |
0 | 0 | 3 | 0-0-3 |
.karte .titel |
0 | 2 | 0 | 0-2-0 |
input[type="text"]:focus-visible |
0 | 2 | 1 | 0-2-1 |
li::marker |
0 | 0 | 2 | 0-0-2 |
#nav a.aktiv |
1 | 1 | 1 | 1-1-1 |
:where(#nav) a |
0 | 0 | 1 | 0-0-1 |
Verglichen wird von links nach rechts. Eine einzige ID mit 1-0-0 schlägt hundert Klassen mit 0-100-0. Erst bei Gleichstand auf allen drei Stellen entscheidet die Reihenfolge im Quelltext, und dort gewinnt die zuletzt notierte Regel.
Kaskade und Reihenfolge
Die Spezifität ist nur eine Stufe der Kaskade. Stand September 2026 prüfen Browser in dieser Reihenfolge:
- Herkunft und Wichtigkeit. Browser-, Nutzer- und Autorenstile stehen in einer festen Rangfolge, die
!importantumkehrt. - Inline-Styles aus dem
style-Attribut. - Kaskadenschichten aus
@layer. Eine spätere Schicht gewinnt gegen eine frühere, unabhängig von der Spezifität. - Spezifität.
- Reihenfolge im Quelltext.
Die Reihenfolge der Stylesheets im <head> wirkt sich damit direkt aus. Welcher Weg der Einbindung wann passt, steht unter CSS einbinden.
Warum ID-Selektoren und !important Probleme verursachen
/* Don't: jede Ausnahme braucht eine noch längere Kette */
#seite #inhalt .karte h2.titel { color: var(--titel); }
.karte h2.titel { color: var(--warnung) !important; }
Die erste Regel liegt bei 2-2-1 und ist nur mit einer weiteren ID zu überschreiben. Die zweite entzieht ihre Deklaration der Kaskade. Jede spätere Anpassung braucht dann ebenfalls ein !important, und am Ende entscheidet allein die Reihenfolge im Quelltext.
/* Do: flache Selektoren mit einer Ebene Klassen */
.karte__titel { color: var(--titel); }
.karte--warnung .karte__titel { color: var(--warnung); }
/* Do: Grundstile in :where() halten die Spezifität auf 0-0-0 */
:where(.prosa) a { color: var(--link); }
Lange Nachfahren-Ketten koppeln das Stylesheet außerdem an eine bestimmte Verschachtelungstiefe. Ändert jemand das Markup, greift die Regel nicht mehr. Wie solche Container-Türme entstehen, beschreibt der Text über Div-Soup vermeiden. In Layouts aus Flexbox und Grid genügt eine Klasse am Container und eine an den Kindern.
CSS verschachtelte Selektoren: Nesting ohne Präprozessor
Verschachtelte Selektoren gehören seit 2023 zum Sprachumfang von CSS. Stand September 2026 unterstützen Chrome, Edge, Firefox und Safari sie in ihren aktuellen Versionen. Ein Präprozessor ist dafür nicht mehr nötig.
.karte {
padding: 1rem;
& > h2 { margin-block-start: 0; }
a {
color: var(--link);
&:focus-visible { outline: 3px solid var(--fokus); }
}
@media (width >= 40rem) { padding: 2rem; }
}
Das kaufmännische Und verweist auf den umgebenden Selektor. Seit Ende 2023 verstehen die Browser auch verschachtelte Regeln, die direkt mit einem Elementnamen beginnen. Eine Falle steckt in der Spezifität: Der verschachtelte Selektor verhält sich wie :is() über die Elternliste und übernimmt deren stärksten Teil.
/* Achtung: die innere Regel erbt die Spezifität von #start */
.seite, #start {
& .karte { padding: 1rem; } /* rechnet als 1-0-1 */
}
Als Faustregel bleibt es bei zwei Ebenen. Jede weitere Ebene erzeugt Selektoren, deren Spezifität im Browser-Inspektor niemand mehr im Kopf nachrechnet.
Barrierefreiheit: worauf es ankommt
Der Fokusring ist die sichtbarste Stelle, an der Selektoren über Bedienbarkeit entscheiden. Ein pauschales outline: none entfernt ihn für alle Eingabearten und verstößt gegen 2.4.7 Fokus sichtbar. :focus-visible löst den Zielkonflikt, der früher zu dieser Zeile geführt hat.
/* Don't: entfernt den Fokusring auch für Tastatur und Sprachsteuerung */
*:focus { outline: none; }
/* Do: sichtbarer Ring dort, wo er gebraucht wird */
:focus-visible { outline: 3px solid var(--fokus); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }
Der Ring braucht gegenüber dem Hintergrund ein Kontrastverhältnis von mindestens 3:1 nach 1.4.11 Nicht-Text-Kontrast. Geprüft wird das mit der Tabulatortaste über die ganze Seite. Den Ablauf beschreibt Tastatur und Fokus.
:hover allein reicht für kein Bedienelement aus. Wer mit der Tastatur arbeitet, erreicht den Zustand nur über :focus-visible. Jede Hover-Regel für einen Button, einen Link oder eine Karte bekommt deshalb ein Gegenstück für den Fokus. Erscheint beim Zeigen oder Fokussieren zusätzlicher Inhalt wie ein Tooltip, gelten die Bedingungen aus 1.4.13 Inhalt bei Hover oder Fokus. Der Inhalt muss dann ausblendbar, mit dem Zeiger erreichbar und beständig sein.
Text aus content in ::before und ::after steht in einer Grauzone. Ein Teil der Screenreader liest ihn vor, ein anderer Teil überspringt ihn. Verlässliche Information gehört deshalb ins Markup und nie in das Stylesheet. Generierter Text lässt sich zudem nicht markieren und kopieren, und automatische Übersetzung erfasst ihn nicht. Die Schreibweise content: "★" / "" ergänzt einen alternativen Text für die Barrierefreiheits-Schnittstelle. Die aktuellen Browser unterstützen sie, ältere Versionen ignorieren den Teil hinter dem Schrägstrich.
Icon-Fonts über ::before sind aus demselben Grund fehleranfällig. Fällt die Schrift aus oder ersetzt eine Nutzerin die Schriftart über ein eigenes Stylesheet, bleibt ein Kästchen oder ein zufälliges Zeichen aus dem Unicode-Bereich für private Nutzung stehen. Manche Screenreader lesen dieses Zeichen vor. Ein Inline-SVG mit passender Auszeichnung ist die stabilere Lösung, wie der Text über Icons und SVGs zeigt.
Ein letzter Punkt betrifft die Reihenfolge. :nth-child() zählt die Position im DOM, und die sichtbare Anordnung kann davon abweichen. Wer Elemente mit Flexbox oder Grid umsortiert, verschiebt allein das Bild. Screenreader und Tastatur folgen weiterhin dem Quelltext. Deshalb verlangt 1.3.1 Info und Beziehungen eine programmatisch bestimmbare Struktur. Die eigene Seite prüfst du mit den Schritten aus Barrierefreiheit selbst testen.
Häufige Fragen
Welche CSS-Selektoren gibt es und welche brauche ich im Alltag?
Der Standard kennt Typ-, Klassen-, ID-, Universal- und Attributselektoren, dazu vier Kombinatoren, rund sechzig Pseudoklassen und ein gutes Dutzend Pseudoelemente. Im Alltag kommst du mit Klassen, Typselektoren, dem Kind-Kombinator sowie :hover, :focus-visible und :nth-child() durch fast jede Oberfläche. Die vollständige Liste führt das W3C in der Spezifikation Selectors Level 4.
Wie kombiniere ich zwei CSS-Selektoren?
Ohne Trennzeichen entsteht eine Und-Bedingung: a.knopf trifft Links mit der Klasse knopf. Ein Komma bildet eine Oder-Liste: h1, h2 trifft beide Überschriften. Ein Leerzeichen, >, + oder ~ beschreibt die Lage im Dokumentbaum. Diese drei Arten der Verbindung lassen sich frei mischen, etwa in nav > ul li a:focus-visible.
Wie berechne ich die Spezifität eines CSS-Selektors?
Zähle drei Gruppen getrennt: IDs, dann Klassen samt Attributselektoren und Pseudoklassen, dann Typselektoren und Pseudoelemente. #nav a.aktiv ergibt 1-1-1. Verglichen wird von links nach rechts, sodass eine ID jede Zahl an Klassen schlägt. Bei Gleichstand gewinnt die Regel, die im Stylesheet weiter unten steht.
Was ist der Unterschied zwischen + und ~ bei Geschwistern?
Beide wählen Geschwister aus, die im Dokument nach dem ersten Element stehen. h2 + p trifft allein den Absatz, der unmittelbar auf die Überschrift folgt. h2 ~ p trifft jeden Absatz danach im selben Elternelement. Vorherige Geschwister erreicht keiner von beiden. Dafür brauchst du :has(), etwa p:has(+ h2).
Funktionieren verschachtelte Selektoren ohne Sass?
Ja. CSS Nesting ist seit 2023 Teil der Sprache und funktioniert Stand September 2026 in allen aktuellen Browsern. Ein Build-Schritt ist dafür nicht mehr nötig. Beachte die Spezifität: Eine verschachtelte Regel verhält sich wie :is() über die Elternliste und übernimmt deren stärksten Selektor.
Verwandte Themen
- CSS-Übersicht: der Einstieg in alle CSS-Themen dieser Seite
- CSS einbinden: die drei Wege und ihre Folgen für die Reihenfolge in der Kaskade
- Flexbox und Grid: die Layout-Systeme, in denen flache Klassen-Selektoren genügen
- Tastatur und Fokus: der Test hinter
:focus-visible - Semantik, Barrierefreiheit, SEO und Performance: warum sauberes Markup an mehreren Stellen zugleich zahlt
Quellen
- CSS-Selektoren (MDN Web Docs: Referenz aller Selektortypen mit Browser-Unterstützung)
- Selectors Level 4 (W3C: die Spezifikation, die Kombinatoren, Pseudoklassen und die Berechnung der Spezifität festlegt)
- Spezifität (MDN Web Docs: Rechenregeln und Sonderfälle wie
:where()) - CSS Nesting Module Level 1 (W3C: Syntax der Verschachtelung und ihre Wirkung auf die Spezifität)