Using the Custom Highlight API

Chris Coyier Chris Coyier on

The Custom Highlight API came to my attention recently as Firefox recently started supporting it (Firefox 140, June 2025), which brought support across all the major browsers. With it, you can apply (some) styling to text that you get your hands on in JavaScript via the Range() class. I would say text that you select, but there aren’t really normal selectors involved here, making it rather unusual to work with for a CSS guy like me.

I think a basic word explanation is helpful here, as it sure would have helped me when I first started poking at it:

  1. You need a textNode1. (e.g. document.querySelector("p").firstChild)
  2. Then you need a Range() in which you do a setStart and setEnd on, meaning the range is now between those two integers.
  3. Then you call CSS.highlights.set() on that Range, giving it a name.
  4. Then you use ::highlight()in CSS, passing in that name you just used.

If we had one <p> of text on a page, that whole process looks like this:

const WORD_TO_HIGHLIGHT = "wisdom";
const NAME_OF_HIGHLIGHT = "our-highlight";

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

const startIndex = textContent.indexOf(WORD_TO_HIGHLIGHT);
const endIndex = startIndex + WORD_TO_HIGHLIGHT.length;

const range = new Range();
range.setStart(textNode, startIndex);
range.setEnd(textNode, endIndex);

const highlight = new Highlight(range);
CSS.highlights.set(NAME_OF_HIGHLIGHT, highlight);Code language: JavaScript (javascript)

This is neat to see in DevTools, where the word “wisdom” clearly has custom CSS styling applied to it, but there is no element around that word that you’d normally think would be necessary to apply those styles.

It’s likely what the browser itself does when it needs to apply styling to only certain parts of text, like it does when you use the Find feature baked into browsers.

Here’s that demo:

Why is this useful?

  • Having the ability to target and style text without needing to touch the DOM at all is interesting. Sometimes, DOM APIs are criticized for being slow, so being able to avoid that might be advantageous, particularly if you have to do it a lot.
  • Adding and removing <span>s, aside from potentially being “slow” affects the DOM structure and thus might affect other CSS and JavaScript deal with the DOM.
  • DOM weight can be a performance concern on web pages. Too much DOM, recalcs can be very “expensive” and the UX on the page can suffer with things like slow animations and scrolling.

Here’s a GitHub PR page with just 17 changed files. The page has over 4,500 spans on it already used in things like colorizing the diffs and syntax highlighting. That’s decently heavy, and it can definitely get worse.

I’m sure there are lots more reasons this API exists, but these are just a few reasons that come to mind right away.

Doing a Bit More (Search Example)

Making a new Highlight() accepts multiple Ranges. Meaning a single ::highlight() in CSS can apply to many Ranges of text. This would be useful if we built our own search feature onto a page. If search was a crucial feature of a web app you are building, I can easily see building out your own UI for it rather than relying on the built-in browser feature.

This time, let’s let the word(s) we’re going to find in the text come from the user:

<label>
  Search the text below
  <input type="search" value="oven" id="searchTerm">
</label>Code language: HTML, XML (xml)

Then we’ll listen for changes:

window.searchTerm.addEventListener("input", (e) => {
  doSearch(e.target.value.toLowerCase());
});Code language: JavaScript (javascript)

Note we’re passing the value typed in to a function, and we’re lower-casing it as we do, as search is generally most useful when it’s case insensitive.

Our doSearch function will then accept that search term and run a RegEx across all the text:

const regex = new RegExp(searchTerm, "gi");Code language: JavaScript (javascript)

What we need is an Array of indexes for all the found instances of the text. It’s a bit of a mouthful of code, but here you go:

const indexes = [...theTextContent.matchAll(new RegExp(searchTerm, 'gi'))].map(a => a.index);Code language: JavaScript (javascript)

With that Array of indexes, we can loop over them creating Ranges, then send all the Ranges to a new Highlight.

const arrayOfRanges = [];

indexes.forEach(matchIndex => {
  // Make a "Range" out of the index values.
  const searchRange = new Range();
  searchRange.setStart(par, matchIndex);
  searchRange.setEnd(par, matchIndex + searchTerm.length);

  arrayOfRanges.push(searchRange);
})

const ourHighlight = new Highlight(...arrayOfRanges);
CSS.highlights.set("search-results", ourHighlight);Code language: JavaScript (javascript)

All together, it makes a functional search experience:

For Syntax Highlighting

It feels like syntax highlighting code is a pretty good use case for this API. André Ruffert has already taken that idea and ran with it, making a <syntax-highlight> Web Component which uses Prism.js by Lea Verou to tokenize the code, but then doesn’t apply <span>s like out-of-the-box Prism does, it uses this custom highlight API instead.

Example:

I think this is awesome, but it’s noteworthy that this API is only possible client-side. And for something like syntax highlighting, that can mean a delay between seeing the code and having the syntax-highlighting “pop in”. I admit I prefer server-side rendered syntax highlighting when possible. Meaning if you can serve a bunch of <span>s from the server in code like this (and it doesn’t affect performance or accessibility badly) then that’s probably better.

I also admit I’m still somewhat obsessed with fonts with built-in syntax highlighting, which feels like untapped territory for font foundries to jump on.


  1. I’m glad I learned about .firstChild the other day, and how it selects the textNode of otherwise childless text elements ↩︎

It's time to take your JavaScript to the next level

Frontend Masters logo

Frontend Masters is the best place on the web to really learn JavaScript. We have a complete learning path from the biggest and best teachers in JavaScript to help you make the most out of the web's biggest language.

7-Day Free Trial

Leave a Reply

Your email address will not be published. Required fields are marked *

$871,946

Frontend Masters donates to open source projects through thanks.dev and Open Collective, as well as donates to non-profits like The Last Mile, Annie Canons, and Vets Who Code.