Library / SDK
bvaughn/react-highlight-words avatar
bvaughn/react-highlight-words

react-highlight-words wraps every non match in a span, and that has consequences

React component to highlight words within a larger body of text

2,303 stars175 forksTypeScriptMIT

At a glance

What is it?
react-highlight-words highlights search terms inside a block of text and does so by splitting the string into marked and unmarked runs. That split is the design, and it drives most of its prop surface: separate tags, classes and styles for matched and unmatched text, plus a sanitize hook and a pluggable chunk finder.
Who is it for?
Use react-highlight-words when you need to render one string with visible matches and nothing else, because its props cover styling, an active match cursor, accent insensitive matching and custom match logic. Do not use it for search across a document, for fuzzy or whole word matching, which it does not offer, or with untrusted terms and autoEscape set to false, since the docs warn a malicious pattern can hang the render.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The split into mark and span runs

The component takes a block of text and an array of search terms, and returns React nodes rather than an HTML string. Matches are wrapped in a mark element by default, and every remaining stretch of text is wrapped in a span. Both defaults are replaceable through highlightTag and unhighlightTag, and both are also available as class name or inline style props: highlightClassName, highlightStyle, unhighlightClassName, unhighlightStyle.

That split is not incidental decoration. Because unmatched text lives inside its own elements, a container's flex or grid layout will treat each run as a separate item, and any CSS selector that assumes contiguous text nodes stops matching. Anything you apply to the outer wrapper, including arbitrary props such as title or data-* attributes, lands on the single wrapper span, and a ref is forwarded to that same element.

The element is a span either way, so display behaviour is up to your stylesheet. If you need block semantics for a paragraph of prose, you have to set that yourself.

Installation and the smallest useful example

One package, one import:

sh
npm install react-highlight-words

TypeScript definitions ship inside the published dist folder, and package.json routes them separately for bundlers and for CommonJS, with ./dist/react-highlight-words.d.ts for import and ./dist/react-highlight-words.d.cts for require. The package is marked sideEffects false and ships only dist, so it is tree shakeable and adds nothing beyond the component.

The minimal usage passes two required props:

tsx
import Highlighter from "react-highlight-words";

function Example() {
  return (
    <Highlighter
      highlightClassName="YourHighlightClass"
      searchWords={["and", "or", "the"]}
      textToHighlight="The dog is chasing the cat. Or perhaps they're just playing?"
    />
  );
}

What you see is each occurrence of those three terms wrapped, with the rest of the sentence in plain spans. Note that the search is substring based rather than word based: a term like "the" matches inside longer words too, and the component offers no word boundary option. If you need whole word matches, that logic has to come from findChunks.

One rendering detail the documentation calls out is easy to lose. Browsers collapse whitespace and newlines by default, so to preserve the formatting of the source string you need style={{ whiteSpace: "pre-wrap" }} on the component.

autoEscape is the prop to think about before you pass user input

String search terms are matched literally by default, which is what autoEscape true does: it escapes the term so characters such as a dot or a parenthesis cannot turn into pattern syntax. Setting it to false treats strings as regular expressions instead, and the documentation qualifies that: strings which are not valid regular expressions are then matched literally, so a malformed pattern degrades rather than throwing.

The compiled expressions do not carry the u flag, so syntax requiring it, the documentation gives \p{L} as the example, works only when you pass an actual RegExp object rather than a string.

The warning is the part to keep in view. The docs state that autoEscape should only be set to false for trusted search terms, because a malicious regular expression can take an extremely long time to run. In a component that runs during render, that is a denial of service on the client thread, so if your terms come from a search box on a shared page, leave the default in place.

Case behaviour differs between the two input kinds. String terms follow the caseSensitive prop; RegExp terms are case sensitive unless they carry the i flag themselves.

sanitize handles accents, and keeps indices pointing at the original string

The sanitize prop exists for text where the reader's spelling and the stored spelling diverge, most obviously accents. It processes each string search word and the text to highlight before comparing them.

The subtle part is the index contract. Matches always refer to the original text, even when sanitizing changes its length, and the documentation's example is Æ expanding to AE. Without that guarantee a highlight range computed on sanitized text would slice the wrong part of the real string and produce garbled output, so this is the kind of detail that separates a usable normalisation hook from a broken one.

Sanitizing should transform characters independently. If your function changes length in a way that depends on neighbouring characters, it is applied to each character separately instead. RegExp search terms are not themselves sanitized, but they are matched against the sanitized text, which is the asymmetry to remember if you mix input kinds.

findChunks replaces the matcher, and highlight-words-core is where it lives

The findChunks prop is the extension point that makes the component more than a substring marker. It receives the search options with defaults already applied and returns the start and end index of every range to highlight plus the searchIndex of which term matched. Overlapping ranges are combined automatically, so your function does not have to reconcile a term matching inside another term's span.

The default implementation lives in a separate package, highlight-words-core, and the documentation links to it as the reference. That is worth noticing when weighing the dependency: the matching algorithm is not in this repository, so custom matching logic means depending on that package's contract for what the options object looks like.

The callback shape is what makes this useful in practice. Fuzzy matching, whole word boundaries, diacritic folding or domain specific tokenisation all reduce to writing a function that returns ranges, and the rendering side stays untouched. Highlighting across markup is not supported, since textToHighlight is a plain string.

Rendering an active match without rewriting the component

Search results need a cursor, and three props provide one. activeIndex names the match to treat as current, activeClassName applies a class to it and activeStyle applies inline styles to it. The documentation pairs each with a note: use activeClassName or activeStyle alongside activeIndex, which is what makes the feature opt in rather than always on.

highlightClassName is more flexible than it first appears. It accepts either a single class name or an object mapping search terms to class names, so different terms can look different. Object keys are compared against the matched text first and then against the string search term that produced the match, case insensitively unless caseSensitive is set. That ordering matters when your term list contains different capitalisations of the same word.

highlightTag also accepts a React component, class or function, not just a tag name. A custom component receives children, className, highlightIndex, searchIndex and style, which gives you per match index state if you need it, for example to attach a tooltip or an id to the match you just clicked. unhighlightTag takes a component the same way, though it is described only as accepting a class or function component without listing the props it forwards.

Upgrading past 0.x, and when a different approach fits better

The repository jumped from 0.21.1 to 1.0.0 on the same day, both tagged on 2026-10-11, after v0.21.0 in January 2025. Review CHANGELOG.md in the repository root before upgrading. The last push was on 2026-10-11 as well.

Licensing is MIT, which permits commercial use subject to its licence conditions.

The alternative for a page that wants a proper find in long text is the browser's own window find, which needs no component at all. For search across a document rather than within one string, you need an index and a result list, which this component does not attempt. And if your highlighting needs markup aware traversal, splitting on HTML tags yourself, or stemming and ranking, you are past what a string prop can express. What you gain by depending on this package is the sanitize index guarantee, the overlap merging and the active match plumbing, which is real but bounded.

The repository itself is a documentation site as much as a library: index.tsx, public/ and a docs build target alongside the library build suggest the example site is part of the same tree, and CONTRIBUTING.md with husky and prettier config are present at the root.

Editorial conclusion

Use react-highlight-words when you need to render one string with visible matches and nothing else, because its props cover styling, an active match cursor, accent insensitive matching and custom match logic. Do not use it for search across a document, for fuzzy or whole word matching, which it does not offer, or with untrusted terms and autoEscape set to false, since the docs warn a malicious pattern can hang the render. Verify first that highlight-words-core is a dependency you are comfortable with, since findChunks delegates the matching to it.

Frequently asked questions

How do I install react-highlight-words?

Run npm install react-highlight-words. TypeScript definitions are included in the published dist folder, with separate type files for ESM imports and CommonJS require.

What HTML element does react-highlight-words use for matches?

Matches are wrapped in a mark element by default and unhighlighted text in a span. Both are replaceable through the highlightTag and unhighlightTag props, which also accept React components.

Is it safe to pass user input as searchWords?

Keep autoEscape at its default of true, which matches string terms literally. The documentation warns that setting it to false lets a malicious regular expression run for an extremely long time, so only disable it for trusted terms.

How do I highlight a match differently from the others?

Pass activeIndex for the index of the match to treat as current, together with activeClassName or activeStyle. Separately, highlightClassName accepts an object that maps individual search terms to different class names.

Can react-highlight-words match without accents?

Yes, through the sanitize prop, which transforms each string search word and the text before comparing. Match indices still refer to the original text even when sanitizing changes the string length.

Does react-highlight-words support fuzzy or custom matching?

Yes, via the findChunks prop, which receives the search options and returns the start and end index of each range plus the searchIndex of the matching term. The default implementation lives in the highlight-words-core package.

Official sources

  1. bvaughn/react-highlight-words on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bvaughn-react-highlight-words.svg)](https://hysenlabs.com/projects/bvaughn-react-highlight-words)