The CSS Custom Highlight API is a powerful feature that allows web developers to programmatically highlight text ranges without modifying the DOM structure.
Unlike traditional methods that wrap text in span elements, this API maintains the original document structure while applying styles to specific text ranges.
Developers can use JavaScript to define the ranges they want to highlight and then style those highlights using CSS. This approach is more performant and maintainable compared to creating and managing multiple DOM elements.
Key Benefits
One significant advantage of the Custom Highlight API is that highlights automatically adjust when the text content changes. This makes it ideal for applications like text editors, search functionality, or syntax highlighting.
Another important feature is the ability to create multiple independent highlight groups with different styles. For instance, you could highlight search results in yellow, keywords in blue, and important information in red.
Browser Support
As of 2025, the CSS Custom Highlight API is supported in most modern browsers including Chrome, Edge, Firefox, and Safari. For older browsers, developers should implement fallback solutions using traditional span-based highlighting.
Demo
The CSS Custom Highlight API allows developers to highlight text ranges without modifying the DOM structure. This makes it ideal for search functionality, text editors, and syntax highlighting. Unlike traditional methods that wrap text in span elements, this API maintains the original document structure while applying styles to specific text ranges.
## Implementation
To use this API, you'll need to:
- Create Range objects that define the text to highlight
- Add these ranges to a Highlight object
- Register the highlight with a name using
CSS.highlights.set()
- Style the highlight using the
::highlight()
pseudo-element in CSS
Basic Example
Here's a complete example that highlights all occurrences of a specific word in a paragraph:
const paragraph = document.querySelector('#content');
const searchTerm = 'highlight';
const ranges = [];
const textContent = paragraph.textContent;
let startPos = 0;
while (true) {
const pos = textContent.indexOf(searchTerm, startPos);
if (pos === -1) break;
const range = new Range();
const textNode = paragraph.childNodes[0];
range.setStart(textNode, pos);
range.setEnd(textNode, pos + searchTerm.length);
ranges.push(range);
startPos = pos + 1;
}
const searchHighlight = new Highlight(...ranges);
CSS.highlights.set('search-results', searchHighlight);
Code Breakdown:
First, we select the paragraph element with the ID 'content' and define our search term 'highlight'.
We create an empty array called ranges
to store all the text ranges we want to highlight.
The while loop searches for all occurrences of our search term in the paragraph:
textContent.indexOf(searchTerm, startPos)
finds the position of the next occurrence
- When no more occurrences are found (
pos === -1
), we break out of the loop
For each occurrence, we create a new Range
object:
range.setStart(textNode, pos)
marks the beginning of the range
range.setEnd(textNode, pos + searchTerm.length)
marks the end of the range
- We assume the paragraph contains a simple text node as its first child
We add each range to our array and update the startPos
to continue searching after the current occurrence.
After finding all ranges, we create a new Highlight
object with all our ranges using the spread operator.
Finally, we register our highlight with the name 'search-results' in the CSS.highlights registry so we can style it with CSS.
And the corresponding CSS to style the highlights:
::highlight(search-results) {
background-color: #FFFF00;
color: #000000;
}
CSS Breakdown:
- The
::highlight()
pseudo-element is used to style highlighted ranges
search-results
is the name we registered in the JavaScript code
- We're setting a yellow background (
#FFFF00
) and black text (#000000
) for our highlights
Advanced Usage
Multiple Highlight Groups
One of the most powerful features of the Highlight API is the ability to maintain multiple independent highlight groups.
This means you can have different styled groups of highlighted elements allowing for near-infinite customization.
const keywordHighlight = new Highlight(...keywordRanges);
const errorHighlight = new Highlight(...errorRanges);
const selectionHighlight = new Highlight(...selectionRanges);
CSS.highlights.set('keywords', keywordHighlight);
CSS.highlights.set('errors', errorHighlight);
CSS.highlights.set('selection', selectionHighlight);
Code Breakdown:
We create three separate Highlight
objects, each containing different sets of ranges:
keywordRanges
might contain ranges for programming keywords
errorRanges
might contain ranges where syntax errors occur
selectionRanges
might contain the user's current text selection
We register each highlight group with a unique name in the registry:
- 'keywords' for highlighting programming keywords
- 'errors' for highlighting syntax errors
- 'selection' for highlighting the user's selection
By keeping these highlight groups separate, we can apply different styles to each type of highlight and manage them independently.
With corresponding CSS:
::highlight(keywords) {
background-color: #E6F7FF;
font-weight: bold;
}
::highlight(errors) {
background-color: #FFF1F0;
text-decoration: wavy underline red;
}
::highlight(selection) {
background-color: rgba(0, 100, 255, 0.3);
}
CSS Breakdown:
Dynamic Highlighting
The API shines when used for dynamic content:
function updateSearchHighlights(searchTerm) {
CSS.highlights.delete('search-results');
if (!searchTerm.trim()) return;
const content = document.querySelector('#content');
const textContent = content.textContent;
const ranges = [];
const regex = new RegExp(searchTerm, 'gi');
let match;
const textNode = content.childNodes[0];
while ((match = regex.exec(textContent)) !== null) {
const range = new Range();
range.setStart(textNode, match.index);
range.setEnd(textNode, match.index + searchTerm.length);
ranges.push(range);
}
if (ranges.length > 0) {
const searchHighlight = new Highlight(...ranges);
CSS.highlights.set('search-results', searchHighlight);
}
}
document.querySelector('#search-input').addEventListener('input', (e) => {
updateSearchHighlights(e.target.value);
});
Code Breakdown:
We define a function updateSearchHighlights
that refreshes the highlighted text whenever the user types a new search term:
First, we delete any existing highlights with the name 'search-results':
- This ensures we start fresh with each new search
- If the search term is empty, we simply return without creating new highlights
We prepare for our search by:
- Getting the content element and its text
- Creating an empty array to store our text ranges
- Setting up a regular expression with flags 'gi' for global and case-insensitive search
We then perform a regex search to find all matches:
- The
while
loop with regex.exec()
finds all occurrences, even overlapping ones
- For each match, we create a range from its starting index to its ending index
- Each range is added to our array
If we found any matches, we:
- Create a new Highlight object with all our ranges
- Register it with the name 'search-results' in the CSS.highlights registry
Finally, we connect our function to the search input's 'input' event:
- This triggers the highlight update every time the user types
- The search results update in real-time as the user types
The Custom Highlight API offers significant performance benefits compared to traditional DOM-based highlighting methods:
- Reduced DOM Complexity: No need to insert additional elements, keeping the DOM clean
- Efficient Updates: Highlights update automatically with content changes
- Browser Optimization: The browser can optimize rendering of highlights
- Lower Memory Usage: Fewer DOM nodes means less memory consumption
Fallback for Older Browsers
For browsers that don't support the Custom Highlight API, you can still implement a fallback:
function highlightText(element, searchTerm) {
if (window.CSS && CSS.highlights) {
// Modern approach with Custom Highlight API
const textContent = element.textContent;
const ranges = [];
const regex = new RegExp(searchTerm, 'gi');
let match;
const textNode = element.childNodes[0];
while ((match = regex.exec(textContent)) !== null) {
const range = new Range();
range.setStart(textNode, match.index);
range.setEnd(textNode, match.index + searchTerm.length);
ranges.push(range);
}
if (ranges.length > 0) {
const searchHighlight = new Highlight(...ranges);
CSS.highlights.set('search-results', searchHighlight);
}
} else {
// Fallback to traditional span-based highlighting
const text = element.innerHTML;
const regex = new RegExp(`(${searchTerm})`, 'gi');
element.innerHTML = text.replace(regex, '<span class="highlight">$1</span>');
}
}
Code Breakdown:
We create a function that checks if the browser supports the Custom Highlight API:
if (window.CSS && CSS.highlights)
detects if the feature is available
If the API is supported:
- We implement the modern approach using Ranges and the Highlight object
- This keeps the DOM structure clean and performs better
If the API is not supported:
- We fall back to the traditional method of wrapping matched text in
<span>
elements
const text = element.innerHTML
gets the HTML content of the element
text.replace(regex, '<span class="highlight">$1</span>')
wraps each match in a span
- We use a capture group in the regex and reference it with
$1
in the replacement
The fallback approach requires a corresponding CSS rule:
.highlight {
background-color: #FFFF00;
color: #000000;
}
This function gives us the best of both worlds:
- Modern browsers get the performance benefits of the Custom Highlight API
- Older browsers still get highlighted text, just with a different implementation
Use Cases
The Custom Highlight API is particularly useful for:
- Text Editors: Highlighting syntax, errors, or selected text
- Search Functionality: Highlighting search results in long documents
- Educational Tools: Highlighting grammar errors or difficult words
- Code Documentation: Highlighting important code sections
- Content Analysis: Marking named entities, keywords, or sentiment
Conclusion
The Custom Highlight API represents an important step forward in web development, providing more efficient and flexible ways to enhance text presentation and interaction.
By leveraging this API, developers can create more sophisticated text-based interfaces without compromising on performance or maintainability.
As browser support continues to improve, this API will likely become the standard approach for text highlighting across the web, replacing the traditional span-based methods that have been used for decades.