Menu

Understanding the CSS Custom Highlight API

Understanding the CSS Custom Highlight API

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:

  1. Create Range objects that define the text to highlight
  2. Add these ranges to a Highlight object
  3. Register the highlight with a name using CSS.highlights.set()
  4. 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:

  1. First, we select the paragraph element with the ID 'content' and define our search term 'highlight'.

  2. We create an empty array called ranges to store all the text ranges we want to highlight.

  3. 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
  4. 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
  5. We add each range to our array and update the startPos to continue searching after the current occurrence.

  6. After finding all ranges, we create a new Highlight object with all our ranges using the spread operator.

  7. 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:

  1. 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
  2. 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
  3. 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:

  • Each highlight group gets its own distinct styling:

    • Keywords have a light blue background and bold text
    • Errors have a light red background with a wavy red underline
    • Selection has a semi-transparent blue background
  • This approach allows for visual distinction between different types of highlights in the same document

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:

  1. We define a function updateSearchHighlights that refreshes the highlighted text whenever the user types a new search term:

  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

Performance Considerations

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:

  1. 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
  2. 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
  3. 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
  4. The fallback approach requires a corresponding CSS rule:

    .highlight {
      background-color: #FFFF00;
      color: #000000;
    }
    
  5. 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.

Walter Guevara is a Computer Scientist, software engineer, startup founder and previous mentor for a coding bootcamp. He has been creating software for the past 20 years.
AD: "Heavy scripts slowing down your site? I use Fathom Analytics because it’s lightweight, fast, and doesn’t invade my users privacy." - Get $10 OFF your first invoice.

Community Comments

No comments posted yet

Code Your Own Classic Snake Game – The Right Way

Master the fundamentals of game development and JavaScript with a step-by-step guide that skips the fluff and gets straight to the real code.

Ad Unit

Current Poll

Help us and the community figure out what the latest trends in coding are.

Total Votes:
Q:
Submit

Add a comment