CSS · DOM · Highlighting · Accessibility
CSS Custom Highlights API
Text markieren ohne DOM-Mutation

Suchbegriffe hervorheben, Diff-Änderungen visualisieren, Spracherkennungs-Treffer markieren — bisher erforderte das alles das Einwickeln von Textknoten in span-Elemente, was den DOM korrumpiert und Barrierefreiheit gefährdet. Die CSS Custom Highlights API löst das Problem elegant: Ranges definieren, registrieren, per ::highlight() stylen — fertig.

11 Min. Lesezeit ::highlight() · Range · HighlightRegistry · CSS.highlights Chrome 105+ · Firefox 117+ · Safari 17.2+

1. Das Problem mit span-Injection für Text-Highlighting

Die traditionelle Methode, Text auf einer Webseite zu markieren — zum Beispiel Suchtreffer in einem Artikel hervorzuheben — ist das Einwickeln der Textstelle in ein <span>-Element mit einer Highlight-Klasse. Das klingt simpel, hat aber ernste Nebenwirkungen. Erstens wird der DOM-Baum mutiert: Textknoten werden aufgespalten, neue Elemente eingefügt und das semantische Markup verändert. Das bricht möglicherweise vorhandene JavaScript-Eventlistener, die auf spezifische DOM-Knoten referenzieren. Zweitens ist die Rückgängigmachung aufwendig: Alle eingefügten <span>-Elemente müssen wieder entfernt und die Textknoten zusammengeführt werden — fehleranfällig und langsam.

Die CSS Custom Highlights API — offiziell CSS Custom Highlight API — löst dieses Problem auf einem völlig anderen Weg. Statt den DOM zu manipulieren, werden JavaScript-Range-Objekte definiert, die Textbereiche beschreiben, und in einem globalen CSS.highlights-Register gespeichert. Das entsprechende ::highlight(name)-Pseudo-Element im Stylesheet übernimmt dann das visuelle Styling. Der DOM bleibt unangetastet — keine Textknoten werden aufgespalten, keine neuen Elemente eingefügt. Das macht die CSS Custom Highlights API zu einer semantisch sauberen, performanten und Accessibility-freundlichen Alternative zu span-Injection.

2. Architektur der CSS Custom Highlights API

Die CSS Custom Highlights API besteht aus drei aufeinander aufbauenden Schichten. Die erste Schicht sind Range-Objekte — die Standard-DOM-API, die einen beliebigen Textbereich durch Start-Knoten, Start-Offset, End-Knoten und End-Offset beschreibt. Ranges sind nicht neu; sie wurden schon immer für Browser-eigene Textauswahl verwendet. Die Custom Highlights API öffnet diesen Mechanismus für benutzerdefinierte Anwendungsfälle.

Die zweite Schicht ist das Highlight-Objekt — ein Set von Range-Objekten mit optionaler Priorität. Mehrere Ranges können zu einem Highlight zusammengefasst werden, was besonders bei Suchfunktionen nützlich ist, die viele Treffer gleichzeitig hervorheben. Die dritte Schicht ist CSS.highlights — ein globales HighlightRegistry-Objekt, das Highlight-Objekte unter benutzerdefinierten Namen speichert. Jeder Name im Register korrespondiert mit einem ::highlight(name)-Pseudo-Element im Stylesheet. Diese drei Schichten zusammen — Range, Highlight, HighlightRegistry — bilden das vollständige System der CSS Custom Highlights API.


/* CSS Custom Highlights API — stylesheet side */

/* Register a custom highlight named "search-result" */
::highlight(search-result) {
  background-color: #c4b5fd;  /* violet highlight */
  color: #1e1b4b;             /* dark text for contrast */
}

/* Multiple highlight types — different visual treatments */
::highlight(search-active) {
  background-color: #7c3aed;  /* current match — stronger */
  color: #ffffff;
  border-radius: 2px;         /* limited properties supported */
}

::highlight(diff-added) {
  background-color: #bbf7d0;  /* green for additions */
  color: #14532d;
}

::highlight(diff-removed) {
  background-color: #fecaca;  /* red for removals */
  color: #7f1d1d;
  text-decoration: line-through;
}

/* Highlight with priority — higher z-index wins visually */
::highlight(grammar-error) {
  text-decoration: wavy underline #ef4444;
  /* Only a subset of CSS properties are allowed in ::highlight() */
  /* background-color, color, text-decoration, outline, caret-color */
}

3. Range-Objekte: Textbereiche präzise definieren

Ein Range-Objekt beschreibt einen Textbereich im DOM ohne diesen zu verändern. Mit document.createRange() wird ein leeres Range-Objekt erzeugt. Über range.setStart(node, offset) und range.setEnd(node, offset) wird der Bereich definiert: node ist ein Textknoten, offset ist die Zeichenposition innerhalb des Textknotens. Für die CSS Custom Highlights API müssen die Ranges immer auf Textknoten zeigen — nicht auf Element-Knoten, obwohl die Range-API Element-Nodes grundsätzlich auch unterstützt.

Um alle Vorkommen eines Suchbegriffs in einem Dokument zu finden und als Ranges zu speichern, wird typischerweise eine Kombination aus TreeWalker und String-Matching verwendet. Der TreeWalker traversiert alle Textknoten im Dokument, und für jeden Textknoten wird mit indexOf oder einem regulären Ausdruck nach dem Suchbegriff gesucht. Für jeden Treffer wird ein neues Range-Objekt erstellt. Diese Ranges werden dann gesammelt und als Highlight in der Registry gespeichert. Die gesamte Suche findet im JavaScript statt; der DOM wird nie berührt — das ist der entscheidende Vorteil der CSS Custom Highlights API.


/* Allowed CSS properties in ::highlight() pseudo-element */
/* Source: CSS Custom Highlight API Level 1 specification  */

::highlight(example) {
  /* ✓ Background */
  background-color: rgba(196, 181, 253, 0.4);

  /* ✓ Text color */
  color: #1e1b4b;

  /* ✓ Text decoration */
  text-decoration: underline wavy #7c3aed;
  text-decoration-thickness: 2px;

  /* ✓ Outline */
  outline: 2px solid #4a1d96;
  outline-offset: 2px;

  /* ✓ Caret color (for editable content) */
  caret-color: #7c3aed;

  /* ✗ NOT allowed — will be silently ignored */
  /* border-radius: 4px; */
  /* padding: 2px 4px; */
  /* font-weight: bold;  */
  /* display: inline-block; */
  /* box-shadow: ...; */
}

/* Priority: higher number wins when highlights overlap */
/* Set via Highlight constructor: new Highlight(...ranges, { priority: 1 }) */
/* Default priority is 0 */

4. HighlightRegistry und CSS.highlights

CSS.highlights ist das globale HighlightRegistry-Objekt — ein Map-ähnliches Interface mit den Methoden set(name, highlight), get(name), has(name), delete(name) und clear(). Jeder Name ist ein String, der direkt mit dem Parameter des ::highlight(name)-Pseudo-Elements im Stylesheet korrespondiert. Das Namens-Mapping ist case-sensitive. Ein Name wie "search-result" im JavaScript korrespondiert exakt mit ::highlight(search-result) im CSS — mit einem Bindestrich, ohne Anführungszeichen im CSS.

Die CSS Custom Highlights API ist reaktiv: Wenn neue Ranges zum Highlight-Objekt hinzugefügt oder entfernt werden, aktualisiert der Browser die visuelle Darstellung automatisch, ohne dass das Stylesheet neu geladen werden muss. Das macht die API ideal für Echtzeit-Features wie Live-Suche: Bei jeder Eingabe werden die alten Ranges aus dem Highlight-Objekt entfernt oder das Highlight-Objekt wird neu gesetzt, und der Browser rendert die aktualisierte Markierung sofort. Das Highlight-Objekt selbst ist ebenfalls ein Set mit den Methoden add(range), delete(range) und clear().

5. ::highlight() Pseudo-Element und erlaubte Eigenschaften

Das ::highlight()-Pseudo-Element ist der CSS-Teil der CSS Custom Highlights API. Es funktioniert ähnlich wie das Browser-eigene ::selection-Pseudo-Element, das die visuelle Darstellung von Textauswahlen steuert. Wie ::selection unterstützt ::highlight() nur eine eingeschränkte Menge von CSS-Eigenschaften: background-color, color, text-decoration, text-shadow, outline und caret-color. Eigenschaften wie padding, border-radius, font-weight und display sind nicht erlaubt und werden stillschweigend ignoriert.

Diese Einschränkung ist kein Bug, sondern Design: Die erlaubten Eigenschaften können vom Browser angewendet werden, ohne das Layout neu berechnen zu müssen. Ein background-color oder eine Textfarbe verändert die Größe und Position des Elements nicht — der Browser kann sie als reinen Repaint anwenden, ohne einen Reflow zu triggern. Das erklärt auch, warum die CSS Custom Highlights API im Vergleich zu span-Injection mit padding und border-radius performanter ist: Es gibt keinen Reflow durch DOM-Mutation, und das Styling selbst löst ebenfalls keinen Reflow aus. Für Anwendungsfälle, die echte geometrische Rahmen um Highlights erfordern — wie in Code-Editoren — bleibt canvas oder eine überlagerte Ebene die einzige Alternative.


/* JavaScript side: using the CSS Custom Highlights API */
/* This code shows the JS integration pattern alongside CSS */

/*
// Feature detection
if (!CSS.highlights) {
  console.warn('CSS Custom Highlights API not supported');
  // Fall back to span injection
}

// Create ranges for all matches of a search term
function highlightText(searchTerm, containerEl) {
  // Clear previous highlights
  CSS.highlights.clear();

  if (!searchTerm) return;

  const ranges = [];

  // Walk all text nodes in the container
  const walker = document.createTreeWalker(
    containerEl,
    NodeFilter.SHOW_TEXT
  );

  let node;
  const term = searchTerm.toLowerCase();

  while ((node = walker.nextNode())) {
    const text = node.textContent.toLowerCase();
    let start = 0;

    while ((start = text.indexOf(term, start)) !== -1) {
      const range = new Range();
      range.setStart(node, start);
      range.setEnd(node, start + term.length);
      ranges.push(range);
      start += term.length;
    }
  }

  // Register highlight — name matches ::highlight(search-result) in CSS
  if (ranges.length > 0) {
    const highlight = new Highlight(...ranges);
    CSS.highlights.set('search-result', highlight);
  }
}
*/

/* CSS receives the highlight name and applies visual styles */
::highlight(search-result) {
  background-color: rgba(196, 181, 253, 0.6);
  color: #2e1065;
}

6. Praxis: Echtzeit-Suchfunktion mit Custom Highlights

Die Echtzeit-Suchfunktion ist der häufigste Anwendungsfall der CSS Custom Highlights API. In einem Blog-Artikel oder einer Dokumentationsseite kann eine Suchleiste implementiert werden, die bei jeder Eingabe alle Vorkommen des Suchbegriffs im Artikeltext markiert — ohne den Text zu verändern, ohne die Seite neu zu laden, ohne den DOM zu mutieren. Das Grundmuster: Ein input-Event-Listener auf dem Suchfeld ruft bei jeder Änderung eine Funktion auf, die alle Textknoten im Dokument traversiert, Treffer als Ranges sammelt, ein Highlight-Objekt erstellt und es unter dem registrierten Namen in CSS.highlights speichert.

Ein fortgeschrittenes Muster kombiniert zwei verschiedene Custom Highlights: search-result für alle Treffer und search-active für den aktuell fokussierten Treffer. Mit den Pfeiltasten kann der Nutzer zwischen den Treffern navigieren; der aktive Treffer bekommt ein visuell stärkeres Styling. Das search-active-Highlight enthält immer genau einen Range, der mit der aktuellen Position aktualisiert wird. Der aktive Treffer wird mit range.startContainer.parentElement.scrollIntoView() in den sichtbaren Bereich gescrollt. Das Ergebnis ist eine vollwertige Suche-im-Dokument-Funktion, wie sie Ctrl+F im Browser bietet — implementiert in unter 50 Zeilen JavaScript und zwei CSS-Regeln.

7. Diff-Darstellung und Textvergleiche

Ein weiterer wichtiger Anwendungsfall der CSS Custom Highlights API ist die Darstellung von Textunterschieden — Diffs. In Code-Review-Tools, Dokumenten-Versionierungssystemen oder Übersetzerungs-UIs werden häufig zwei Versionen desselben Textes nebeneinander dargestellt, wobei hinzugefügte Wörter grün und entfernte Wörter rot markiert werden. Mit span-Injection ist das aufwendig: Die Diff-Berechnung liefert Zeichenpositionen, die dann in DOM-Mutationen übersetzt werden müssen — mit Berücksichtigung bereits vorhandener HTML-Tags im Text.

Die CSS Custom Highlights API vereinfacht diesen Prozess erheblich. Die Diff-Bibliothek (z.B. diff-match-patch oder Myers-Diff-Algorithmus) liefert eine Liste von Änderungsoperationen mit Zeichenpositionen. Diese Positionen werden direkt in Range-Objekte übersetzt und in zwei Highlight-Objekten — diff-added und diff-removed — gesammelt. Zwei CSS-Regeln übernehmen das visuelle Styling. Das DOM des angezeigten Textes bleibt vollständig unverändert — keine Textknoten werden aufgespalten, keine Tags werden eingefügt. Das macht die Implementierung robuster und einfacher zu warten, besonders wenn der Ursprungstext bereits HTML-Markup enthält.

8. Accessibility und Screenreader-Verhalten

Die CSS Custom Highlights API hat gegenüber span-Injection einen entscheidenden Accessibility-Vorteil: Der DOM wird nicht verändert, also wird auch die semantische Struktur nicht verändert. Ein Screenreader, der den Text vorliest, liest denselben Text vor — mit und ohne aktive Highlights. Bei span-Injection hingegen können eingefügte <span>-Elemente den Vorlesefluss unterbrechen, besonders wenn sie mitten in einem Wort eingefügt werden, was bei Zeichenoffset-basiertem Matching leicht passiert. Ein Screenreader, der Textknoten einzeln vorliest, kann durch aufgespaltene Textknoten inkonsistentes Verhalten zeigen.

Die Spezifikation der CSS Custom Highlights API sieht vor, dass ::highlight()-Stile keine ARIA-Eigenschaften oder Accessibility-Tree-Änderungen auslösen. Das ist korrekt für rein visuelle Highlights wie Suchmarkierungen — der Nutzer sieht die Hervorhebung, aber sie hat keine semantische Bedeutung, die ein Screenreader kommunizieren müsste. Wenn Highlights semantische Bedeutung tragen sollen — etwa „dieser Text enthält einen Fehler" — muss zusätzlich ein ARIA-Attribut gesetzt werden. Das kann auf dem Element-Level geschehen, ohne den Textknoten zu mutieren: element.setAttribute('aria-description', 'Enthält Grammatikfehler').

9. Custom Highlights vs. span-Injection im Vergleich

Die Wahl zwischen CSS Custom Highlights API und span-Injection bestimmt maßgeblich, wie performant, wartbar und Accessibility-konform eine Text-Highlighting-Implementierung ist. Die Unterschiede sind in allen drei Dimensionen messbar.

Kriterium span-Injection CSS Custom Highlights API Vorteil
DOM-Mutation Ja — Textknoten werden aufgespalten Nein — DOM bleibt unverändert Custom Highlights
Performance Reflow durch DOM-Änderung Nur Repaint, kein Reflow Custom Highlights
Eventlistener-Stabilität Verlust bei Textknoten-Aufspaltung Stabil — keine Knotenänderung Custom Highlights
Accessibility Kan Vorlesefluss unterbrechen DOM-Struktur bleibt semantisch Custom Highlights
CSS-Flexibilität Alle CSS-Eigenschaften möglich Nur eingeschränkte Properties span-Injection

Die Einschränkung bei den erlaubten CSS-Eigenschaften ist der einzige echte Nachteil der CSS Custom Highlights API. Wer abgerundete Ecken oder Padding um Highlights benötigt, muss einen anderen Weg gehen. Für alle anderen Anwendungsfälle — Farbe, Unterstreichung, Outline — ist die CSS Custom Highlights API der klare Gewinner. Browser-Support: Chrome 105+, Firefox 117+ und Safari 17.2+. Für ältere Browser ist eine Feature-Detection mit if ('highlights' in CSS) und span-Injection als Fallback empfohlen.

Mironsoft

Modernes CSS, Progressive Enhancement und Frontend-Architektur

Text-Features ohne DOM-Hacks implementieren?

Wir implementieren moderne Browser-APIs wie die CSS Custom Highlights API in euren Projekten — mit Feature-Detection, graceful Fallback und vollständiger Accessibility-Konformität.

API-Integration

Custom Highlights API, View Transitions, Container Queries in reale Projekte bringen

Accessibility-Audit

DOM-Mutations-Analyse, ARIA-Korrektheit und Screenreader-Testing

Frontend-Architektur

Progressive Enhancement, Feature Detection und Browser-Kompatibilitäts-Strategie

10. Zusammenfassung

Die CSS Custom Highlights API ist die moderne, semantisch saubere Antwort auf das klassische Problem der Text-Markierung ohne DOM-Mutation. Das dreistufige System — Range-Objekte für Textbereiche, Highlight-Objekte als Ranges-Container, CSS.highlights-Registry als Brücke zum Stylesheet — ermöglicht visuelle Textmarkierungen, die der DOM-Struktur nichts hinzufügen und nichts wegnehmen. ::highlight(name) im Stylesheet übernimmt das visuelle Styling mit einer eingeschränkten, aber ausreichenden Menge an CSS-Eigenschaften für alle typischen Highlight-Anwendungsfälle.

Die Einsatzgebiete sind vielfältig: Live-Suchfunktionen, Diff-Darstellungen, Grammatikprüfungs-UIs, Spracherkennungs-Treffer, Code-Annotation-Tools. In allen diesen Fällen ist die CSS Custom Highlights API der span-Injection überlegen — in Performance (kein Reflow), in Accessibility (stabiler DOM), in Wartbarkeit (kein Cleanup nötig) und in Robustheit (keine Eventlistener-Verluste durch DOM-Mutation). Browser-Support ist seit 2023/2024 in allen modernen Browsern gegeben; Feature-Detection und span-Fallback sichern ältere Umgebungen ab.

CSS Custom Highlights API — Das Wichtigste auf einen Blick

Range erstellen

new Range(), setStart(textNode, offset), setEnd(textNode, offset). Beschreibt Textbereich ohne DOM-Mutation.

Registry befüllen

new Highlight(...ranges), dann CSS.highlights.set('name', highlight). Name entspricht ::highlight(name) im Stylesheet.

CSS stylen

::highlight(name) { background-color; color; text-decoration; outline } — eingeschränkte Properties, kein Reflow.

Fallback

if ('highlights' in CSS) — span-Injection als Fallback für ältere Browser. Feature Detection ist Pflicht.

11. FAQ: CSS Custom Highlights API

1Was ist die CSS Custom Highlights API?
Ranges definieren Textbereiche → Highlight-Objekt sammelt Ranges → CSS.highlights registriert unter Name → ::highlight(name) im CSS stylt. Kein DOM wird verändert.
2Besser als span-Injection?
Kein DOM-Reflow, keine Textknoten-Aufspaltung, keine Eventlistener-Verluste, kein Cleanup. Semantisch stabiler DOM für Screenreader.
3Welche CSS-Properties in ::highlight()?
background-color, color, text-decoration, text-shadow, outline, caret-color. Kein padding, border-radius oder font-weight — werden ignoriert.
4Mehrere Highlights gleichzeitig?
Jedes Highlight bekommt einen eigenen Namen in CSS.highlights. Mehrere Highlights sind unabhängig voneinander aktiv.
5Überlappende Highlights?
Das Highlight mit höherer Priorität gewinnt. Priorität beim Highlight-Objekt setzen. Bei gleicher Priorität: zuletzt registriertes gewinnt.
6Browser-Support?
Chrome 105+, Edge 105+, Firefox 117+, Safari 17.2+. Feature Detection: if ('highlights' in CSS). span-Fallback für ältere Browser.
7Highlights entfernen?
CSS.highlights.clear() — alles. CSS.highlights.delete('name') — einzelnes Highlight. highlight.clear() — Ranges leeren.
8Custom Highlights in Shadow DOM?
Ranges können auf Shadow-DOM-Textknoten zeigen, aber ::highlight()-Styles müssen im gleichen Stylesheet-Kontext stehen. Cross-Shadow-Styling ist eingeschränkt.
9Alle Textvorkommen im DOM finden?
TreeWalker mit SHOW_TEXT traversiert alle Textknoten. indexOf oder Regex für Matching. Für jeden Treffer einen Range erstellen.
10Custom Highlights im Druck?
::highlight()-Stile werden beim Drucken angewendet wenn Hintergrundgrafiken aktiviert. Für Druckhighlights besser color und text-decoration statt background-color verwenden.