Using the CSS Custom Highlight API with JavaScript

The CSS Custom Highlight API provides a performant, native way to style arbitrary document text ranges using JavaScript without modifying the underlying DOM tree. This article covers the core mechanics of the API, explaining how to define DOM Range objects, bundle them into a Highlight instance, register them in the global CSS.highlights map, and apply visual styles using the CSS ::highlight() pseudo-element.

How the Custom Highlight API Works

Historically, styling specific text ranges—such as search result matches, spellcheck errors, or collaborative editing cursors—required wrapping text inside inline HTML elements like <span> or <mark>. This DOM mutation is computationally expensive and can disrupt accessibility and layout.

The CSS Custom Highlight API decouples text selection from DOM mutation by connecting programmatic JavaScript ranges directly to the CSS rendering engine through four key steps:

  1. Creating Ranges (Range): JavaScript identifies the text boundaries in the DOM and creates one or more standard Range objects.
  2. Instantiating a Highlight (Highlight): The Range objects are passed to a new Highlight() instance.
  3. Registering the Highlight (CSS.highlights): The highlight is registered with a unique custom name in the CSS.highlights registry map.
  4. Applying Styles (::highlight()): CSS targets the registered identifier using the ::highlight(name) pseudo-element to apply colors, text decorations, or background styles.

Step-by-Step Implementation

1. Identify and Create Text Ranges

First, use standard DOM methods to locate text nodes and create Range objects for the start and end offsets you want to style.

const paragraph = document.querySelector("p");
const textNode = paragraph.firstChild;

const range1 = new Range();
range1.setStart(textNode, 0);
range1.setEnd(textNode, 5);

const range2 = new Range();
range2.setStart(textNode, 10);
range2.setEnd(textNode, 18);

2. Create and Register the Highlight Object

Pass the ranges to the Highlight constructor. A single Highlight object can contain multiple ranges. Then, add the highlight object to the global CSS.highlights registry using a custom string identifier.

// Bundle ranges into a Highlight object
const customHighlight = new Highlight(range1, range2);

// Register it under the name "search-results"
CSS.highlights.set("search-results", customHighlight);

3. Style the Highlight in CSS

In your stylesheet, use the ::highlight() pseudo-element along with your chosen identifier to declare the styling rules.

::highlight(search-results) {
  background-color: #ffeb3b;
  color: #000000;
  text-decoration: underline;
}

Supported CSS Properties

Because custom highlights operate similarly to the ::selection pseudo-element, only a subset of CSS properties can be applied:

Properties that alter layout or geometry (such as margin, padding, display, or font-size) are not supported within ::highlight().

Dynamic Updates and Lifecycle Management

The Highlight object acts as a Set-like collection. Modifying the ranges inside an active Highlight automatically updates the rendered styles on the screen without requiring a re-registration:

// Add a new range dynamically
customHighlight.add(newRange);

// Remove a range
customHighlight.delete(range1);

// Clear all highlights
CSS.highlights.delete("search-results");

This reactive design makes the CSS Custom Highlight API ideal for high-frequency operations, such as live search filtering, syntax highlighting, and code annotation tools.