CSS: Grundlagen

CSS in HTML einbinden

CSS bindest du auf drei Wegen in HTML ein: als externe Datei mit <link rel="stylesheet">, als <style>-Block im <head> oder als style-Attribut am Element. Ab der zweiten Seite eines Projekts ist die externe CSS-Datei der Standardweg.

Die Frage nach dem Einbinden ist eine Frage nach der Wartbarkeit. Eine externe Datei formatiert hundert Seiten aus einer Quelle und liegt nach dem ersten Aufruf im Cache. Ein style-Attribut formatiert genau ein Element und wiederholt sich in jeder Zeile mit derselben Gestaltung.

Diese Seite zeigt die drei Wege mit lauffähigem Code, dazu die Regeln für Pfade und Kaskade, das Einbinden von Schriften und die Stellen mit Folgen für die Ladezeit. Wie der <head> insgesamt aufgebaut ist, steht in der Referenz zu den Dokument-Metadaten im head.

Das Wichtigste in Kürze

  • Der Standardweg ist eine externe CSS-Datei: <link rel="stylesheet" href="../css/style.css"> im <head>. Sie gilt für beliebig viele Seiten und liegt nach dem ersten Aufruf im Cache.
  • Ein <style>-Block im <head> lohnt sich für kritisches CSS, also für die Regeln, die den ohne Scrollen sichtbaren Bereich formatieren. Der Rest kommt aus der Datei.
  • Das style-Attribut gewinnt gegen jeden Selektor deines Stylesheets. Überschreiben lässt es sich nur mit einer Deklaration, die !important enthält.
  • Bei gleicher Spezifität gewinnt die zuletzt eingebundene Regel. Die Reihenfolge der <link>-Elemente im Quelltext entscheidet.
  • @import verzögert den Seitenaufbau, weil der Browser die erste Datei erst laden und parsen muss, bevor er die zweite entdeckt.
  • Google Fonts gehören auf den eigenen Server. Das Landgericht München I hat am 20. Januar 2022 (Az. 3 O 17493/20) für die dynamische Einbindung 100 Euro Schadensersatz zugesprochen.

CSS einbinden in HTML: die drei Wege

CSS extern einbinden: die eigene CSS-Datei

Beim externen Weg stehen alle Regeln in einer Datei mit der Endung .css. Ein <link>-Element im <head> verweist darauf.

<!doctype html>
<html lang="de">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Externe CSS-Datei einbinden</title>
    <link rel="stylesheet" href="/css/style.css" />
  </head>
  <body>
    <h1>Überschrift</h1>
    <p class="fliesstext">Ein Absatz.</p>
  </body>
</html>
/* /css/style.css */
:root {
  --textfarbe: #1a1a1a;
}

body {
  color: var(--textfarbe);
  font-family: system-ui, sans-serif;
  line-height: 1.6;
}

Zwei Attribute genügen. rel="stylesheet" benennt die Beziehung, href den Pfad. Das früher übliche type="text/css" ist seit HTML5 überflüssig.

Ein Stylesheet im <head> blockiert das Rendern. Der Browser zeigt die Seite erst an, wenn die Datei geladen und geparst ist. Ohne diese Sperre blitzt für einen Moment die unformatierte Seite auf.

Internes CSS mit dem style-Element einbinden

Beim internen Weg steht das CSS im Dokument selbst. Ein <style>-Element im <head> bindet CSS ohne eine zweite Datei ein.

<head>
  <meta charset="utf-8" />
  <title>Internes CSS</title>
  <style>
    body {
      margin: 0;
      font-family: system-ui, sans-serif;
    }

    .hinweis {
      border-left: 4px solid #0b5d3b;
      padding: 0.75rem 1rem;
    }
  </style>
</head>

Das spart einen Netzwerk-Aufruf. Der Preis ist die fehlende Wiederverwendung. Jede weitere Seite bekommt eine eigene Kopie, und der Browser kann den Block nicht getrennt vom HTML zwischenspeichern. Sinnvoll bleibt er für kritisches CSS, Fehlerseiten und E-Mail-Templates.

Inline-Styles: das style-Attribut am Element

Der dritte Weg schreibt die Deklarationen an das Element.

<p style="color: #a10f0f; font-weight: 700;">Bitte prüfe die Eingabe.</p>

Das Attribut wirkt auf genau dieses Element und steht in der Kaskade über jedem Selektor deines Stylesheets. Selbst #hauptbereich .warnung p verliert dagegen. Darüber kommt nur eine Deklaration mit !important hinweg. Damit eignen sich Inline-Styles für Werte, die erst zur Laufzeit feststehen, etwa die Breite eines Fortschrittsbalkens.

Do und Don’t im direkten Vergleich

Das folgende Muster wiederholt dieselben drei Deklarationen in jeder Zeile. Eine neue Schriftgröße bedeutet eine Suchen-und-Ersetzen-Aktion über alle Dateien.

<!-- Don't: die Gestaltung steht am Element und wiederholt sich -->
<p style="color:#1a1a1a;font-size:18px;line-height:1.6">Absatz eins</p>
<p style="color:#1a1a1a;font-size:18px;line-height:1.6">Absatz zwei</p>
<p style="color:#1a1a1a;font-size:18px;line-height:1.6">Absatz drei</p>

Als Klasse in der externen Datei bleibt dieselbe Gestaltung an einer Stelle änderbar. Die Schriftgröße in rem folgt der Grundschrift des Browsers und erfüllt damit 1.4.4 Textgröße ändern.

<!-- Do: die Gestaltung steht im Stylesheet, das Element hat eine Klasse -->
<link rel="stylesheet" href="/css/style.css" />
<p class="fliesstext">Absatz eins</p>
<p class="fliesstext">Absatz zwei</p>
.fliesstext {
  color: #1a1a1a;
  font-size: 1.125rem;
  line-height: 1.6;
}

Welcher Weg wann passt

Weg Passt für Kostet
Externe Datei Websites ab zwei Seiten, Design-System, Teamarbeit einen zusätzlichen Netzwerk-Aufruf beim ersten Besuch
<style> im <head> kritisches CSS, Einzelseiten, HTML-Mails Wiederverwendung und eigenen Cache-Eintrag
style-Attribut Werte aus JavaScript, berechnete Positionen Wartbarkeit, Freigabe in der Content-Security-Policy

Pfade richtig angeben

Ein relativer Pfad startet im Verzeichnis des HTML-Dokuments. Zwei Punkte gehen eine Ebene nach oben.

<!-- Dokument: /produkte/index.html -->
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="../css/style.css" />

Ein führender Schrägstrich startet an der Wurzel der Domain. Dieser wurzelrelative Pfad funktioniert aus jedem Verzeichnis gleich und ist die robustere Wahl.

<link rel="stylesheet" href="/css/style.css" />

Eine absolute URL zeigt auf eine fremde Domain. Sie verursacht einen zusätzlichen DNS-Lookup samt eigenem TLS-Handshake und gibt die IP-Adresse deiner Besucher an einen Dritten weiter.

Mehrere Stylesheets einbinden und die Kaskade

Ein weiteres CSS-Stylesheet einbinden heißt: eine weitere <link>-Zeile schreiben. Alle Dateien laden parallel.

<link rel="stylesheet" href="/css/reset.css" />
<link rel="stylesheet" href="/css/basis.css" />
<link rel="stylesheet" href="/css/komponenten.css" />

Angewendet werden die Regeln in der Reihenfolge des Quelltextes. Bei gleicher Spezifität gewinnt die Regel aus komponenten.css. Was Spezifität bedeutet und wie du sie niedrig hältst, steht bei den CSS-Selektoren.

Die Rangfolge der Herkunft legt die CSS-Spezifikation zu Kaskade und Vererbung fest. Zuerst gelten die Vorgaben des Browsers, darüber die des Nutzers, darüber deine. Bei !important dreht sich diese Folge um. Eine wichtige Deklaration aus einem Nutzer-Stylesheet gewinnt gegen jede wichtige Deklaration von dir.

Seit 2022 unterstützen alle aktuellen Browser Kaskadenebenen. Mit @layer legst du die Rangfolge ganzer Stylesheets fest, ohne über die Spezifität zu gehen.

@layer reset, basis, komponenten;

@layer basis {
  a {
    color: #12467a;
  }
}

@import und warum es Ladezeit kostet

@import holt eine weitere CSS-Datei aus einer CSS-Datei heraus.

/* Don't: /css/style.css lädt die Dateien nacheinander */
@import url("basis.css");
@import url("komponenten.css");

Der Browser lädt style.css, parst sie, findet dort die beiden @import-Zeilen und startet erst dann die nächsten Aufrufe. Die Dateien laden nacheinander statt parallel. Der Preload-Scanner, der das HTML vorab nach ladbaren Ressourcen absucht, sieht Adressen innerhalb einer CSS-Datei nicht. Über zwei Ebenen kommt so eine halbe Sekunde zusammen, die direkt in den Messwert LCP einfließt.

<!-- Do: die Dateien laden parallel -->
<link rel="stylesheet" href="/css/basis.css" />
<link rel="stylesheet" href="/css/komponenten.css" />

@import steht am Anfang der Datei, vor allen anderen Regeln außer @charset und der Ebenen-Deklaration. Im Build-Prozess ist die Regel unproblematisch, weil Sass, PostCSS oder Vite die Dateien zusammenfassen, bevor der Browser sie sieht.

Das media-Attribut bindet ein Stylesheet an eine Bedingung.

<link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/print.css" media="print" />
<link rel="stylesheet" href="/css/breit.css" media="(min-width: 60rem)" />

Der Browser lädt alle drei Dateien. Blockierend ist nur, was zur aktuellen Umgebung passt. Auf einem schmalen Display laden print.css und breit.css mit niedriger Priorität. Daraus ergibt sich ein Muster für unkritisches CSS.

<link rel="stylesheet" href="/css/rest.css" media="print" onload="this.media='all'" />
<noscript><link rel="stylesheet" href="/css/rest.css" /></noscript>

Die Datei lädt ohne zu blockieren und gilt nach dem Ladeereignis für alle Medien. Ohne JavaScript bleibt sie auf print stehen. Deshalb steht die normale <link>-Zeile im <noscript>.

CSS-Font einbinden: @font-face und lokale Google Fonts

Eine eigene Schrift meldest du mit @font-face an. Alle Deskriptoren listet die MDN-Referenz zu @font-face.

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: "Inter", system-ui, sans-serif;
}

font-display: swap zeigt den Text sofort in der Ersatzschrift und tauscht ihn nach dem Laden aus. Ohne diese Angabe bleibt der Text bis zu drei Sekunden unsichtbar. Als Format genügt woff2. Stand September 2026 unterstützt jeder gepflegte Browser es.

Wer die Schrift früh braucht, lädt sie vor.

<link rel="preload" as="font" type="font/woff2" href="/fonts/inter-regular.woff2" crossorigin />

Das crossorigin-Attribut ist auch bei Dateien von der eigenen Domain Pflicht, weil Schriften im anonymen CORS-Modus geladen werden. Ohne das Attribut lädt der Browser die Datei ein zweites Mal.

Google Fonts lokal einbinden

Die Einbindung über fonts.googleapis.com liefert bei jedem Seitenaufruf die IP-Adresse deiner Besucherinnen und Besucher an Google. Das Landgericht München I hat darin am 20. Januar 2022 (Az. 3 O 17493/20) einen Verstoß gegen die DSGVO gesehen und 100 Euro Schadensersatz zugesprochen. Stand September 2026 ist das die maßgebliche Entscheidung für die deutsche Praxis. Diese Einordnung ersetzt keine Rechtsberatung.

Der Ausweg ist der eigene Server. Du lädst die woff2-Dateien der gewünschten Schnitte herunter, legst sie unter /fonts/ ab und meldest sie mit @font-face an.

<!-- Don't: der Aufruf gibt die IP jedes Besuchers an einen Dritten -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter&display=swap" />
<!-- Do: die Schrift liegt auf dem eigenen Server -->
<link rel="preload" as="font" type="font/woff2" href="/fonts/inter-regular.woff2" crossorigin />
<link rel="stylesheet" href="/css/style.css" />

Danach verlässt keine Anfrage mehr deine Domain.

preload für kritisches CSS

Kritisches CSS sind die Regeln, die den ohne Scrollen sichtbaren Bereich formatieren. Ein <link rel="preload" as="style"> vor dem normalen Stylesheet bringt wenig, weil ein <link rel="stylesheet"> im <head> ohnehin hohe Priorität bekommt. Der Gewinn entsteht aus einem eingebetteten Block und einem asynchron geladenen Rest.

<style>
  /* kritisches CSS, Richtwert unter 14 kB */
  body { margin: 0; font-family: system-ui, sans-serif; }
  header { background: #0b3d2e; color: #fff; padding: 1rem; }
</style>
<link rel="preload" as="style" href="/css/rest.css" onload="this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="/css/rest.css" /></noscript>

Das as-Attribut ist Pflicht. Fehlt es, lädt der Browser die Datei ein zweites Mal. Preload wirkt über Priorität, und zu viele vorgeladene Ressourcen heben sich gegenseitig auf. Was diese Entscheidungen für die Messwerte bedeuten, steht bei den Core Web Vitals.

Barrierefreiheit: worauf es ankommt

Der Inhalt muss ohne CSS verständlich bleiben

Schalte das Stylesheet im Browser ab. Was übrig bleibt, ist die Reihenfolge im DOM. Genau diese Reihenfolge liest ein Screenreader vor, und in dieser Reihenfolge wandert der Tastaturfokus. Ordnet dein CSS die Blöcke visuell um, etwa mit order in Flexbox oder mit expliziten Zeilenangaben in Grid, entstehen zwei verschiedene Reihenfolgen. Das verstößt gegen 1.3.2 Bedeutungstragende Reihenfolge. Der visuelle Aufbau folgt dem DOM. Wo das nicht gelingt, ändert sich das Markup.

Dasselbe gilt für Bedeutung, die allein aus dem Stylesheet kommt. Ein <div> mit font-size: 2rem sieht aus wie eine Überschrift und ist im Dokument keine. Für Hilfsmittel zählt das Element selbst, wie es 1.3.1 Info und Beziehungen verlangt. Zur Rolle der neutralen Elemente siehe div oder span.

Nutzer-Stylesheets und erzwungene Farbmodi

Viele Menschen mit Sehbehinderung überschreiben Farben und Schriftgrößen mit einem eigenen Stylesheet oder mit dem Kontrastmodus des Betriebssystems. Im Windows-Kontrastmodus ersetzt der Browser deine Farben durch das gewählte Systemschema. Dieses Verhalten ist erwünscht. forced-color-adjust: none schaltet den Schutz für ein Element ab und ist nur dort vertretbar, wo die Farbe selbst die Information ist, etwa in einer Farbwahl-Palette. Prüfen lässt sich der Zustand mit @media (forced-colors: active). Wie diese Nutzergruppe arbeitet, beschreibt Sehbehinderung und Blindheit.

!important gegen Nutzereinstellungen ist ein Fehler

Eine Deklaration mit !important in deinem Stylesheet gewinnt gegen jede normale Deklaration eines Nutzer-Stylesheets. Wer eine Mindestschriftgröße oder eigene Farben eingestellt hat, sieht davon dann nichts. Feste Höhen sind der häufigste Fall. Sie verhindern die Anpassung nach 1.4.12 Textabstände, sobald jemand Zeilenhöhe, Buchstaben- und Wortabstand hochsetzt. Nutze !important für Ausnahmen im eigenen Code und niemals gegen Werte, die jemand für sich eingestellt hat.

Inline-Styles blockieren die Anpassung

Gegen ein style-Attribut kommt ein Nutzer-Stylesheet nur mit !important an, und viele Lesehilfen und Browser-Erweiterungen arbeiten ohne diese Kennzeichnung. Eine Seite voller Inline-Farben lässt sich damit kaum umfärben. Wer Farben und Größen im Stylesheet hält, gibt diese Kontrolle zurück.

Systemeinstellungen abfragen

Zwei Medienabfragen holen die Systemeinstellungen in dein CSS.

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --hintergrund: #12141a;
    --textfarbe: #eef1f6;
  }
}

Hier steht !important an der richtigen Stelle, weil es eine Nutzereinstellung durchsetzt. Mehr zu bewegten Elementen steht unter CSS-Animationen. Im dunklen Schema gelten dieselben Kontrastwerte wie im hellen. Rechne sie mit dem Kontrast-Check nach.

Häufige Fragen

Wie kann man CSS extern in HTML einbinden?

Mit einer Zeile im <head>: <link rel="stylesheet" href="../css/style.css">. Die CSS-Datei ist eine reine Textdatei mit der Endung .css und enthält keine HTML-Tags. Der Pfad im href zeigt vom Dokument aus auf die Datei. Ein führender Schrägstrich startet an der Wurzel der Domain und funktioniert aus jedem Verzeichnis heraus gleich.

Warum wird meine CSS-Datei nicht geladen?

Meistens stimmt der Pfad nicht. Öffne die Netzwerk-Analyse der Entwicklerwerkzeuge und sieh nach, ob die Datei mit Status 200 oder mit 404 antwortet. Drei Ursachen decken die Mehrzahl der Fälle ab: Der relative Pfad geht vom falschen Verzeichnis aus, die Schreibweise weicht in der Groß- und Kleinschreibung ab, oder der Browser liefert eine alte Fassung aus dem Cache. Ein Neuladen mit gedrückter Umschalttaste klärt den letzten Fall.

Welche Regel gewinnt, wenn zwei Stylesheets dasselbe Element formatieren?

Bei gleicher Spezifität gewinnt die Regel aus dem Stylesheet, das im Quelltext weiter unten eingebunden ist. Unterscheidet sich die Spezifität, gewinnt der genauere Selektor unabhängig von der Reihenfolge. Über allem steht ein style-Attribut am Element, und darüber wiederum jede Deklaration mit !important. Mit @layer legst du die Rangfolge ganzer Stylesheets selbst fest.

Sind Inline-Styles schlecht?

Für wiederkehrende Gestaltung sind sie die schwächste der drei Möglichkeiten. Sie verhindern Wiederverwendung und Zwischenspeicherung, erschweren Nutzer-Stylesheets und verlangen in einer Content-Security-Policy die Freigabe style-src 'unsafe-inline' oder einen Nonce. Berechtigt sind sie für Werte, die erst zur Laufzeit feststehen, etwa die Breite eines Fortschrittsbalkens oder die Position eines Tooltips.

Wie binde ich Google Fonts DSGVO-konform ein?

Du lädst die Schriftdateien herunter und legst sie auf den eigenen Server. Danach meldest du sie mit @font-face an und verweist im font-family darauf. Damit verlässt keine Anfrage mehr deine Domain. Die Einbindung über fonts.googleapis.com gibt die IP-Adresse jedes Besuchers an Google weiter. Das Landgericht München I hat dafür am 20. Januar 2022 Schadensersatz zugesprochen (Az. 3 O 17493/20).

Verwandte Themen

Gratis E-Book PDF, 37 Seiten HTML & Barrierefreiheit, WCAG & BFSG, GEO & SEO

Kostenloses E-Book

Das Praxishandbuch für sauberes, zugängliches Web

Alles rund um semantisches HTML, Barrierefreiheit, WCAG & BFSG, GEO und SEO — praxisnah und am echten Code. In mehreren Feedbackschleifen von Leserinnen und Lesern verbessert.

  • 3.000+ Downloads
  • 7. Auflage
  • 37 Seiten
  • PDF

Kein Spam. Abmeldung jederzeit mit einem Klick möglich.