Modern Text Search and Highlight Library Using CSS Custom Highlight API

Category: Javascript , Text | November 19, 2025
AuthoryairEO
Last UpdateNovember 19, 2025
LicenseMIT
Views32 views
Modern Text Search and Highlight Library Using CSS Custom Highlight API

React CSS Highlight is a lightweight and high-performance text highlighting library that uses the CSS Custom Highlight API to style search matches without touching the DOM structure.

The library leverages TreeWalker for fast traversal and works across React, Vue, Angular, and vanilla JavaScript applications.

Traditional text highlighting libraries insert wrapper elements around matched text, which triggers layout recalculations and breaks your component hierarchy.

This library takes a different approach. The browser handles the styling through native Range objects and CSS pseudo-elements. You get instant highlighting with no performance penalties from DOM mutations.

More Features:

  • Performance Optimized: Pre-compiled regex patterns run 500 times faster than naive approaches, and requestIdleCallback prevents blocking the main thread during searches.
  • Customizable Styling: Control highlight colors through CSS custom properties and use pre-defined style variants or create custom ::highlight() pseudo-element styles.
  • Full TypeScript Support: Complete type definitions with extensive JSDoc documentation provide autocomplete and type safety throughout your codebase.

Use Cases:

  • Search Result Highlighting: Implement fast, non-destructive text highlighting in search interfaces and document viewers.
  • Code Syntax Highlighting: Create custom syntax highlighting for code editors or documentation with multiple color schemes.
  • Content Analysis Tools: Build text analysis interfaces that highlight specific terms, patterns, or categories in large documents.
  • Accessibility Features: Develop reading assistance tools that highlight text for users with different needs without breaking screen readers.

Installation:

1. Install react-css-highlight with package managers.

# Yarn
$ yarn add react-css-highlight
# NPM
$ npm install react-css-highlight

2. Import the CSS into your project.

import "react-css-highlight/dist/Highlight.css";

Vanilla JS Implementation:

Since vanilla JS lacks React’s reactive state, you must manually call createHighlight again whenever your search term changes or if you modify the DOM content inside the target.

If you are building a Single Page Application (SPA) with a router, remember that the CSS Highlight API persists until you clear it. If you destroy the component or change pages, you should clear the highlights to prevent memory leaks or visual artifacts.

1. Import the vanilla function:

import { createHighlight } from "react-css-highlight/vanilla";

2. Select your target element:

const contentElement = document.getElementById("searchable-content");

3. Initialize the highlighter:

const { matchCount } = createHighlight({
  search: "JavaScript",
  target: contentElement,
  highlightName: "highlight-primary", // Use pre-defined class
  caseSensitive: false,
  wholeWord: true
});
console.log(`Found ${matchCount} matches.`);

4. Detect if the user’s browser supports the CSS Custom Highlight API.

import { isHighlightAPISupported } from "react-css-highlight/vanilla";
if (!isHighlightAPISupported()) {
  // Fallback logic here
  console.warn("CSS Custom Highlight API not supported");
}

5. Customize the styles of the highlighted text using CSS variables. You can set these globally or scoped to a specific container.

/* Override the default CSS Variables */
:root {
  --highlight-primary: #fef3c7;
  --highlight-secondary: #cffafe;
  --highlight-success: #dcfce7;
  --highlight-warning: #fde68a;
  --highlight-error: #ffccbc;
  --highlight-active: #fcd34d;
}
/* OR Create custom styles by defining new ::highlight() pseudo-element rules in your CSS. */
:root {
  /* Default yellow */
  --highlight-primary: #fef3c7;
  /* Custom error color */
  --highlight-error: #ffccbc;
}

React Implementation:

Pattern 1: The Component (Ref-Based)

This is the most performant method. It separates the highlighter from the content. You pass a ref that points to the container you want to search.

import { useRef } from "react";
import Highlight from "react-css-highlight";
function SearchResults() {
  const contentRef = useRef<HTMLDivElement>(null);
  return (
    <>
      {/* The highlighter lives outside the content */}
      <Highlight search="React" targetRef={contentRef} />
      <div ref={contentRef}>
        <p>React is a JavaScript library for building user interfaces.</p>
        <p>React makes it painless to create interactive UIs.</p>
      </div>
    </>
  );
}

Pattern 2: The Wrapper

This is easier for simple use cases. You wrap a single element. Note that the child must accept a ref.

import { HighlightWrapper } from "react-css-highlight";
function SimpleMessage() {
  return (
    <HighlightWrapper search="important">
      <div>
        <p>This is an important message about important topics.</p>
      </div>
    </HighlightWrapper>
  );
}

Pattern 3: The Hook (Advanced)

Use this when you need access to metadata, like the match count or error states. This is great for building “Found X matches” indicators.

import { useRef } from "react";
import { useHighlight } from "react-css-highlight";
function CustomSearch() {
  const contentRef = useRef<HTMLDivElement>(null);
  const { matchCount, isSupported } = useHighlight({
    search: "term",
    targetRef: contentRef,
    highlightName: "highlight-primary"
  });
  return (
    <div>
      <div>Found {matchCount} matches</div>
      <div ref={contentRef}>
        Content goes here...
      </div>
    </div>
  );
}

Configuration Options:

The Highlight component accepts several props for controlling search behavior and highlighting. These options apply to the component, wrapper, and hook patterns.

PropertyTypeDefaultDescription
searchstring or string[]requiredText to highlight with support for multiple search terms
targetRefRefObjectrequiredReact ref pointing to the element containing searchable text
highlightNamestring“highlight”CSS highlight identifier used in ::highlight() pseudo-element
caseSensitivebooleanfalseEnable case-sensitive matching for search terms
wholeWordbooleanfalseMatch complete words only, not partial matches
maxHighlightsnumber1000Maximum number of highlights to prevent performance issues
debouncenumber100Milliseconds to wait before updating highlights after changes
ignoredTagsstring[]undefinedHTML tag names to skip during highlighting
onHighlightChangefunctionundefinedCallback receiving match count when highlights update
onErrorfunctionundefinedError handler receiving Error object if highlighting fails

Hook Return Values:

The useHighlight hook returns an object with three properties that let you build custom UI around the highlighting functionality.

PropertyTypeDescription
matchCountnumberCurrent number of highlighted matches in the target element
isSupportedbooleanBrowser support status for CSS Custom Highlight API
errorError or nullError object if highlighting failed, null when successful

FAQs:

Q: Why aren’t my highlights appearing after implementing the component?
A: Check browser compatibility first since the CSS Custom Highlight API requires Chrome 105+, Safari 17.2+, or Firefox 140+. Verify your targetRef is not null by checking that the component has mounted before the highlight renders. Confirm your search terms exist in the content and aren’t empty strings. Open the browser console to check for errors from the onError callback. Add the onHighlightChange callback to log the match count and verify the search is finding matches.

Q: How do I prevent performance issues when highlighting common terms in large documents?
A: Use the built-in debounce prop set to 300-500 milliseconds to prevent updates on every keystroke. Lower the maxHighlights prop from 1000 to 300-500 for documents with more than 10,000 words. Filter out short terms (less than 3 characters) before passing them to the search prop. Break extremely large documents into smaller sections with separate refs. The library automatically uses requestIdleCallback for non-blocking execution, but you still need to set reasonable limits.

Q: Can I use this library with server-side rendering or static site generation?
A: The library works client-side only since it depends on browser APIs like TreeWalker and CSS.highlights. Your SSR framework needs to hydrate the component after the page loads. The Highlight component renders nothing to the DOM, so it won’t cause hydration mismatches. Set up proper checks for window and document availability if you’re running code during the build step. Consider showing a loading state until the client-side JavaScript initializes the highlighting.

Q: How do I highlight text that updates dynamically or gets added through user actions?
A: The library assumes static content by default. Force a re-render by adding a key prop to the Highlight component that changes when your content updates. The component will remount and recalculate all highlights. For frequently changing content, consider debouncing the key changes to avoid excessive reprocessing. You can also manually trigger updates by changing the search prop to an empty string and back to your search term.

Q: What CSS properties work with the ::highlight() pseudo-element?
A: Firefox support started in June 2025 but with limitations. Firefox cannot use text-decoration or text-shadow properties on highlights. All browsers support background-color, color, and font-weight properties. Safari has a bug where highlights don’t appear when the target element has user-select: none applied. Stick to background-color for maximum browser compatibility, then progressively enhance with additional properties for Chrome and Edge.

You Might Be Interested In:


Leave a Reply