A Guide to the CSS Custom Highlight API

developer in a coffee shop working on developing a code editor with code symbols in the air
Learn how to use the CSS Custom Highlight API to dynamically highlight text ranges on a webpage using JavaScript and CSS. This guide walks you through creating custom highlights for search results, text editors, or any application requiring precise text styling—without altering the DOM.

The CSS Custom Highlight API is a powerful tool that allows developers to style arbitrary text ranges on a webpage using JavaScript and CSS. It extends beyond traditional highlight pseudo-elements like `::selection`, `::spelling-error`, and `::grammar-error`, enabling the creation of custom highlights for text ranges without altering the DOM.

In this blog post, we’ll explore the CSS Custom Highlight API, its key concepts, and how you can use it to highlight text programmatically. By the end, you’ll have a fully functional example that highlights search results on a webpage.

Why Use the CSS Custom Highlight API?

Styling text ranges programmatically is a common requirement for applications like:

  • Text editors: Highlight spelling or grammar errors.
  • Code editors: Highlight syntax errors or specific keywords.
  • Search tools: Highlight search terms in documents. 

The CSS Custom Highlight API makes this process seamless by allowing developers to:

  • Create arbitrary `Range` objects in JavaScript.
  • Define highlights using the `Highlight` interface.
  • Register highlights in the `HighlightRegistry`.
  • Style highlights using the `::highlight()` pseudo-element.

Key Concepts

To use the CSS Custom Highlight API, follow these four steps:

  1. Create `Range` objects: Define the text ranges to style.
  2. Create `Highlight` objects: Associate the ranges with highlights.
  3. Register highlights: Use the `HighlightRegistry` (`CSS.highlights`) to register highlights.
  4. Style highlights: Use the `::highlight()` pseudo-element to define CSS styles.

HTML

				
					<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSS Custom Highlight API Demo</title>
    <link rel="stylesheet" data-wphbdelayedstyle="style.css">
    <script type="wphb-delay-type" src="customhighlight.js" defer></script>
</head>

<body>
    <h1>CSS Custom Highlight API Demo</h1>
    <label>Search within text <input id="query" type="text" /></label>
    <article>
        <p>
            Maxime debitis hic, delectus perspiciatis laborum molestiae labore,
            deleniti, quam consequatur iure veniam alias voluptas nisi quo. Dolorem
            eaque alias, quo vel quas repudiandae architecto deserunt quidem, sapiente
            laudantium nulla.
        </p>
        <p>
            Maiores odit molestias, necessitatibus doloremque dolor illum reprehenderit
            provident nostrum laboriosam iste, tempore perferendis! Ab porro neque esse
            voluptas libero necessitatibus fugiat, ex, minus atque deserunt veniam
            molestiae tempora? Vitae.
        </p>
        <p>
            Dolorum facilis voluptate eaque eius similique ducimus dignissimos assumenda
            quos architecto. Doloremque deleniti non exercitationem rerum quam alias
            harum, nisi obcaecati corporis temporibus vero sapiente voluptatum est
            quibusdam id ipsa.
        </p>
    </article>
<script type="text/javascript" id="wphb-delayed-styles-js">
			(function () {
				const events = ["keydown", "mousemove", "wheel", "touchmove", "touchstart", "touchend"];
				function wphb_load_delayed_stylesheets() {
					document.querySelectorAll("link[data-wphbdelayedstyle]").forEach(function (element) {
						element.setAttribute("href", element.getAttribute("data-wphbdelayedstyle"));
					}),
						 events.forEach(function (event) {
						  window.removeEventListener(event, wphb_load_delayed_stylesheets, { passive: true });
						});
				}
			   events.forEach(function (event) {
				window.addEventListener(event, wphb_load_delayed_stylesheets, { passive: true });
			  });
			})();
		</script></body>

</html>
				
			

CSS

				
					/* Define a custom highlight style */
::highlight(search-results) {
    background-color: #f06;
    color: white;
}
				
			

JS

				
					const query = document.getElementById('query');
const article = document.querySelector('article');

// Find all text nodes in the article. We'll search within
// these text nodes.
const treeWalker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
	allTextNodes.push(currentNode);
	currentNode = treeWalker.nextNode();
}

// Listen to the input event to run the search.
query.addEventListener('input', () => {
	// If the CSS Custom Highlight API is not supported,
	// display a message and bail-out.
	if (!CSS.highlights) {
		article.textContent = 'CSS Custom Highlight API not supported.';
		return;
	}

	// Clear the HighlightRegistry to remove the
	// previous search results.
	CSS.highlights.clear();

	// Clean-up the search query and bail-out if
	// if it's empty.
	const str = query.value.trim().toLowerCase();
	if (!str) {
		return;
	}

	// Iterate over all text nodes and find matches.
	const ranges = allTextNodes
		.map((el) => {
			return { el, text: el.textContent.toLowerCase() };
		})
		.map(({ text, el }) => {
			const indices = [];
			let startPos = 0;
			while (startPos < text.length) {
				const index = text.indexOf(str, startPos);
				if (index === -1) break;
				indices.push(index);
				startPos = index + str.length;
			}

			// Create a range object for each instance of
			// str we found in the text node.
			return indices.map((index) => {
				const range = new Range();
				range.setStart(el, index);
				range.setEnd(el, index + str.length);
				return range;
			});
		});
	// Create a Highlight object for the ranges.
	const searchResultsHighlight = new Highlight(...ranges.flat());

	// Register the Highlight object in the registry.
	CSS.highlights.set('search-results', searchResultsHighlight);
});

				
			

More To Explore

developer in a coffee shop working on developing a code editor with code symbols in the air
Code

A Guide to the CSS Custom Highlight API

Learn how to use the CSS Custom Highlight API to dynamically highlight text ranges on a webpage using JavaScript and CSS. This guide walks you through creating custom highlights for search results, text editors, or any application requiring precise text styling—without altering the DOM.

Share This Post

small_c_popup.png

Need help?

Let's have a chat...


Login

Jump Back In!

Here at Webolution Designs, we love to learn. This includes sharing things we have learned with you. 

Register

Begin Your Learning Journey Today!

Come back inside to continue your learning journey.