Styling Text with the CSS Custom Highlight API

The CSS Custom Highlight API solves a fundamental problem: how to highlight text ranges programmatically without cluttering the DOM with wrapper elements. Whether you’re building search interfaces, text editors, or educational apps, this API offers a cleaner, more performant approach than traditional <mark>
or span-based methods.
Key Takeaways
- The CSS Custom Highlight API enables text highlighting without DOM manipulation
- Highlights work through Range objects, Highlight objects, and the CSS.highlights registry
- Performance improves significantly since no layout recalculations occur
- Multiple non-contiguous ranges can be highlighted simultaneously
What Makes the CSS Custom Highlight API Different
Traditional text highlighting requires modifying the DOM structure. Using <mark>
elements or spans means inserting nodes, triggering layout recalculations, and managing complex HTML when highlights span multiple elements. The CSS Custom Highlight API eliminates these issues by working with JavaScript ranges and the ::highlight
pseudo-element, keeping your DOM pristine.
The API consists of three core components:
- Range objects that define text boundaries
- Highlight objects that group ranges
- CSS.highlights registry that connects highlights to styles
Creating and Styling Text Ranges
Building Your First Highlight
Start by creating a Range object to define which text to highlight:
const range = new Range();
const textNode = document.querySelector('p').firstChild;
range.setStart(textNode, 0);
range.setEnd(textNode, 20);
Next, create a Highlight object and register it:
const highlight = new Highlight(range);
CSS.highlights.set('my-highlight', highlight);
Finally, style it with the ::highlight
pseudo-element:
::highlight(my-highlight) {
background-color: yellow;
color: black;
}
Implementing Search Result Highlighting
Here’s a practical example for highlighting search results across your document:
function highlightSearchResults(searchTerm) {
// Clear existing highlights
CSS.highlights.delete('search-results');
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT
);
const ranges = [];
let node;
while (node = walker.nextNode()) {
const text = node.textContent;
const regex = new RegExp(searchTerm, 'gi');
let match;
while (match = regex.exec(text)) {
const range = new Range();
range.setStart(node, match.index);
range.setEnd(node, match.index + searchTerm.length);
ranges.push(range);
}
}
if (ranges.length > 0) {
const searchHighlight = new Highlight(...ranges);
CSS.highlights.set('search-results', searchHighlight);
}
}
Discover how at OpenReplay.com.
Advanced Patterns: Multi-Range Highlights
The real power emerges when working with multiple, non-contiguous text ranges. A single Highlight object can contain multiple Range objects:
// Highlight multiple separate paragraphs
const range1 = new Range();
range1.selectNodeContents(document.querySelector('#intro'));
const range2 = new Range();
range2.selectNodeContents(document.querySelector('#conclusion'));
const highlight = new Highlight(range1, range2);
CSS.highlights.set('important-sections', highlight);
You can also manage multiple highlight groups simultaneously:
// Different highlight types with distinct styles
CSS.highlights.set('errors', new Highlight(...errorRanges));
CSS.highlights.set('warnings', new Highlight(...warningRanges));
CSS.highlights.set('info', new Highlight(...infoRanges));
::highlight(errors) {
background-color: #fee;
text-decoration: wavy underline red;
}
::highlight(warnings) {
background-color: #ffa;
}
::highlight(info) {
background-color: #e6f3ff;
}
Performance Benefits and Browser Support
The CSS Custom Highlight API excels in performance-critical scenarios. Unlike DOM manipulation, highlights don’t trigger layout recalculations or create memory overhead from additional elements. The browser’s rendering engine handles highlights at the paint layer, making updates remarkably fast.
As of 2025, Chrome, Edge, and Safari support the API, with Firefox support in development. For older browsers, implement a feature detection fallback:
if ('CSS' in window && 'highlights' in CSS) {
// Use Custom Highlight API
const highlight = new Highlight(range);
CSS.highlights.set('fallback-safe', highlight);
} else {
// Fallback to traditional approach
const span = document.createElement('span');
span.className = 'highlight';
range.surroundContents(span);
}
Practical Implementation Tips
When implementing text highlighting in production:
-
Clean up unused highlights to prevent memory leaks:
CSS.highlights.delete('old-highlight');
-
Handle dynamic content by updating ranges when DOM changes:
highlight.add(newRange); highlight.delete(oldRange);
-
Use meaningful highlight names that describe their purpose (e.g., ‘search-results’, ‘spell-check’, ‘user-annotations’)
Conclusion
The CSS Custom Highlight API transforms how we approach text highlighting on the web. By separating highlight logic from DOM structure, it delivers better performance, cleaner code, and more flexibility than traditional methods. Start experimenting with highlight text ranges in your next project—your users (and your DOM) will thank you.
FAQs
Yes, the API can highlight text that spans multiple elements. Range objects can start in one element and end in another, making it perfect for highlighting phrases that cross paragraph or span boundaries.
Highlights remain attached to their original Range objects. If the DOM changes, you need to manually update or recreate the ranges. The API doesn't automatically track DOM mutations, so implement your own observer pattern for dynamic content.
Multiple highlights can overlap the same text range. The browser applies all matching highlight styles, with later-defined styles taking precedence. You can control the visual stacking through CSS properties like background-color and opacity.
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..