Highlight text in JavaScript
Tags: html, javascript
Published: 2021-01-09 07:07:05

---
title: Highlight text in JavaScript
published: true
tags: HTML, JavaScript
canonical_url: https://tomekdev.com/posts/highlight-text-in-javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/f86lwmtmek63j9f0qkyo.png
---

_This post was originally published on https://tomekdev.com/posts/highlight-text-in-javascript. What you see as GIF here is interactive there._ ✌️

----

In the previous post about [search with typo tolerance](https://tomekdev.com/posts/search-with-typo-tolerance), I added a few interactive elements to demonstrate the idea of how we can improve search functionality on the page by being more tolerant to typos. You might be curious how I made highlighting of matching text within results. So here it is.

It's not super complicated but I'll give you a very nice hint you might not know :) **Here is the demo**. Look at the GIF below (or visit my website to play with that) and observe how words are highlighted:

![Highlighting text in JS](https://dev-to-uploads.s3.amazonaws.com/i/onev5gjchf5az3gn5eav.gif)

The trick is to replace all occurrences of searched text with the same text but wrapped with a `<mark>` this time. We will also add a `highlight` CSS class to that `<mark>` so we will be able to style it accordingly. **You don't need any JS library for that.** Here is the code that does the job:

```javascript
const $box = document.getElementById('box');
const $search = document.getElementById('search');

$search.addEventListener('input', (event) => {
  const searchText = event.target.value;
  const regex = new RegExp(searchText, 'gi');

  let text = $box.innerHTML;
  text = text.replace(/(<mark class="highlight">|<\/mark>)/gim, '');

  const newText = text.replace(regex, '<mark class="highlight">$&</mark>');
  $box.innerHTML = newText;
});
```

Let's assume the `$box` is the element that contains text (it could be a whole page) and the `$search` is the input. In line 8 we get the current HTML in the `$box` and remove all current highlights in the following line. We do that to clean-up after ourselves. We don't want to keep old searches (or partial searches) on the screen. You can [play with that on codepen](https://codepen.io/tniezurawski/pen/wvzyVEE) so you'll see the HTML structure and CSS styles (where only the .highlight is important).

The hint I've mentioned before you could potentially miss is `$&` in the second argument of the `replace` method. This is _a special replacement pattern_ that tells the replacer method to _insert the matched substring_ there.

Why we won't simply use something like this? So inserting the searched text?

```javascript
// ...
const searchText = event.target.value;
// ...
const newText = text.replace(
  regex,
  `<mark class="highlight">${searchText}</mark>`
);
```

By doing that **we will get into trouble with the case of the letters**. Most search/find functionality is case insensitive so we don't want to mess with that. Consider the example below, where I simply wrap the searched text with a `<mark>` with that text inside:

![Bad highlighting at work](https://dev-to-uploads.s3.amazonaws.com/i/m9k91wrbshq7p7gkwg9a.gif) 

It's strange, isn't it? **Fortunately, we don't have to be super clever** to keep the case of the matched text. We just need to use `$&` with the `replace` method.

## React implementation

React seems to be the most popular ~~framework~~ library that people use these days. But no matter what front-end framework you use, you'll probably pass `text` as an argument to a component with search-and-highlight functionality. It could be also a label of searchable items on a list.

That simplifies things a bit because we don't have to get a raw text from DOM elements. And we don't have to clean up after ourselves. We can focus on the wrapping part and leave the rendering to the rendering engine:

```jsx
import React, { Component } from 'react';

export default class HighlightText extends Component {
  constructor(props) {
    super(props);
    this.state = { searchText: '' };
    this.search = this.search.bind(this);
  }

  search(event) {
    this.setState({ searchText: event.target.value });
  }

  _getText(text, searchText) {
    return searchText ? this._getTextWithHighlights(text, searchText) : text;
  }

  _getTextWithHighlights(text, searchText) {
    const regex = new RegExp(searchText, 'gi');
    const newText = text.replace(regex, `<mark class="highlight">$&</mark>`);
    return <span dangerouslySetInnerHTML={{ __html: newText }} />;
  }

  render() {
    const { cite, text } = this.props;
    const { searchText } = this.state;
    const textToShow = this._getText(text, searchText);

    return (
      <div className="container">
        <div className="search-container">
          <label htmlFor="search">Search within quoted text</label>
          <input
            id="search"
            placeholder="Type `web` for example"
            type="search"
            autoComplete="off"
            onChange={this.search}
            value={searchText}
          />
        </div>
        <blockquote cite={cite}>{textToShow}</blockquote>
      </div>
    );
  }
}
```
([link to sandbox](https://codesandbox.io/s/zealous-blackburn-54310?file) if you'd like to play with that)

The most important lines in this implementation are lines 20 and 21. The first one is the heart of highlighting implementation and the second makes sure to set _dangerous HTML_ content within an element.

### What's so dangerous about the wrapped searched text?

**Every framework has to sanitize raw HTML** if you plan to display it on the screen. Here we are sure that the content is ok. It's provided by the user but not displayed anywhere else than their computer so it's safe by definition.

Search for "html safe _+ framework name_" to find a way to force the rendering engine to display a wrapped element.

Good luck!

----

EDIT: In the original post, I was wrapping highlighted text with `<span>`. Thanks to the comment below I have changed that to `<mark>` that is semantically better 🙌