Let’s start with the bad news. You can’t actually highlight text in a <textarea>. Any markup you would add to do so would simply display as plain text within the <textarea>. The good news is, with some carefully crafted CSS and JavaScript, you can fake it.

This article will show you how to pull it off, but if you just want the solution, feel free to go straight to the jQuery plugin: highlight-within-textarea.

Highlight within textarea

Why?

Way back in 2010, I made a site called Regex Storm that would highlight regex matches in a <textarea>, like this. It’s not hard to imagine other applications for this sort of “find and highlight” functionality. 5 years is a long time though, so I decided to revisit the concept and fix it up for modern browsers.

The Plan

The basic idea is to carefully position a <div> behind the <textarea>. JavaScript will be used to copy any text entered into the <textarea> to the <div>. A bit more JavaScript will make that both elements scroll as one. With everything perfectly aligned, we can add markup inside the <div> to highlight text, which will show through the <textarea>, completing the illusion.

A demo should make this all clear. Scroll around and edit the text. Then click Toggle Perspective and edit and scroll some more.

See the Pen Highlight Text Inside a Textarea by Will Boyd (@lonekorean) on CodePen.

HTML

It takes a couple of elements to achieve the layout we need.

<div class="container">
  <div class="backdrop">
    <div class="highlights">
      <!-- cloned text with <mark> tags here -->
    </div>
  </div>
  <textarea>
    <!-- user input here -->
  </textarea>
</div>

The container <div> simply serves as an anchor to position other elements within. The highlights <div> is nested within the backdrop <div>, which may seem like 1 more <div> than we need, but doing it this way fixes some subtle scrolling bugs (which we’ll get to later).

The <mark> tag is the best semantic choice for highlighting text inside the highlights <div>. We’ll be inserting these tags with JavaScript.

CSS

Elements must be laid out with pixel perfection. This can be tricky, as many browsers have very subtle differences that normally aren’t worth caring about, but are very noticeable in this case.

For example, Firefox adds a single pixel of margin to the top and bottom of a <textarea>. And iOS adds a border-radius to <textarea>. So we have to fix these styles (and others I haven’t specifically mentioned).

textarea {
  margin: 0;
  border-radius: 0;
}

CSS is used to make text inside the highlights <div> wrap and scroll exactly like the text inside the <textarea>.

.backdrop {
  overflow: auto;
}

.highlights {
  white-space: pre-wrap;
  word-wrap: break-word;
}

It’s also important to set the visibility of things so that our stacked elements display properly together. Remember, the <textarea> sits on top of the highlights <div>.

textarea {
  color: #444; /* or whatever */
  background-color: transparent;
}

.highlights {
  color: transparent;
}

mark {
  color: transparent;
  background-color: #d4e9ab; /* or whatever */
}

.backdrop {
  background-color: #fff; /* or whatever */
}

JavaScript

The JavaScript has 3 main responsibilities.

  1. Keep text in the highlights <div> synced with text in the <textarea>.
  2. Highlight text in the highlights <div> with <mark>.
  3. Make the highlights <div> scroll in tandem with the <textarea>.

Let’s start by binding 2 events.

$textarea.on({
  'input': handleInput,
  'scroll': handleScroll
});

The input event triggers whenever text in the <textarea> is changed. The callback function takes this text, applies highlights to it, then inserts it into the highlights <div>.

function handleInput() {
  var text = $textarea.val();
  var highlightedText = applyHighlights(text);
  $highlights.html(highlightedText);
}

And here’s the applyHighlights() function.

function applyHighlights(text) {
  return text
    .replace(/\n$/g, '\n\n')
    .replace(/[A-Z].*?\b/g, '<mark></mark>');
}

applyHighlights() actually does 2 things. On line 3, it fixes a bug where a trailing carriage return causes the highlights <div> to become misaligned. Then on line 4, it does the actual highlighting by inserting <mark> tags. The regex in this example highlights all capitalized words, but this can be customized.

The scroll callback copies the scroll position of the <textarea> to the backdrop <div>, so that they both scroll in tandem.

function handleScroll() {
  var scrollTop = $textarea.scrollTop();
  $backdrop.scrollTop(scrollTop);
}

Wrap Up

That covers the basic mechanics of (fake) highlighting within a <textarea>. Please note that there are some finer details that this article didn’t discuss and the demo didn’t account for. For the more comprehensive, more bullet-proof solution, check out the highlight-within-textarea jQuery plugin on GitHub.