Using the Browser Highligh API

Published 2025-09-01, About 4 minute read.

The Hightligh API just recently became broadly available!

(Well, at the time of writing this I know it's generally available, so hopefully the above widget information updates!) This API unlocks the ability to highlight portions of text without having to inject or modify DOM.

In this post I'll describe the problems we had with marking DOM text before, the problems Highlight API solves, and how to possibly encapsulate this API in a web component.

So what is the problem with using <mark>?

Turns out, we already have a semantic element for highlighting or marking certain regions of text: the mark element. It's a handy element that has a nice, out-of-the-box styling.

There are some nice libraries out there to help with this task as well and highlight content programatically. Mark.js is one with a straightforward API: hand Mark.js the node and the range to highlight. Mark.js inserts mark tags for you where your text matches the content.

This is perfect if you have a lot of text and you want to programatically find just like the browser:

So what's the issue?

Mark.js searches through the portion of the DOM tree that you give it, searching for text content matches and wrapping those matches in more markup. That extra markup can mess up your styles and your intended DOM structure! Check this example out:

This isn't a contrived example. The structure of these buttons aren't expecting extra mark elements, and flex notices that there's a new child node and properly arranges those new nodes. This sort of thing happened at work when I tried to just use Mark.js on a larger content area that had tabs and tab panels. The tab layout and the content in the panels would jump around because new mark elements were being added from someone (me), and you could argue that we shouldn't have to change DOM internally to get this highlight effect.

What's even worse is there's no way to have the mark elements say "ignore me, please!" If we set mark to have display: contents, we lose the highlighting color.

The Highlight API

The Highlight API lets us manually mark regions of text as "highlighted", and highlight these special regions using a ::highlight selector. There are no new elements being added to the DOM.

In the example below, whenever the search string is changed, we regex search the content of the blockquote. From the matchAll results, we can pull out the character indices of where the string matches occur. For each of those ranges, we create a new Range, and feed those ranges to Highlight

Works like a charm! We're not going to run into issues with injected markup becuase there is none!

Let's make our lives a bit easier...

Admittedly, that's a lot of manual work to highlight text. Perhaps we could encapsulate some of this work by creating a web component that can take in text and an array of ranges to highlight, and do it for us?

Yes, it's a bit of work to get going, but now we can simply do this in a lit template, for example:

And the important thing is that these ranges are reactive!

Let's see this with a live search...

You may ask, "Why not pass a search term to the highlighter?" That's not a bad idea, but we would be coupling the searching strategy with the highlighting. We could take the example above and instead of doing a regex match, use something like fuse.js to do a fuzzy search. Fuse.js provides index ranges as well, so we could just grab those and assign them to our highligter ranges property.

One thing to note here is that every instance of high-lighter shares the same highlight instance and highlight registry. You have to create a highlight instance and register it with CSS.highlights.set. We use only one highlight instance so that if you're customizing the highlighting style using ::highlight() psuedo-element, you would only have to target one highlight name.

That's it!

I hope the examples have spurred some creativity and made the Highlight API a little more understandable. Let me know what you think! Find me on Bluesky or Mastodon. I also have an RSS feed here

⬅️ Previous Post Next Post ➡️
Back to top