Using different colours in native textareas via a "mirror" element
16 Apr 2025
You'll have seen various code editors implement syntax highlighting. How do they do this? There's a few approaches, but one common one involves a "mirror" element. In this tutorial, we'll see how it works.
Here's our HTML:
Within our container, we have our textarea
and a div
, which will act as the mirror.
The way this will work is as follows. Our field and mirror will be given identical styling, and will be absolutely positioned. They'll occupy the same space, and the textarea will have its colour set to transparent, so we see the mirror below.
This is key, because it means the user is interacting with the field, even though it's hidden, but it's the mirror that they'll see, mimicking the field content.
Since the mirror is a div
, not a field, this means we can implement colour highlighting in it, via some simple JavaScript.
Next, our CSS:
/* container */
#container { position: relative; }
/* textarea and mirror */
textarea, #mirror {
width: 400px;
height: 200px;
font-family: monospace;
font-size: 16px;
line-height: 1.5;
padding: 8px;
border: 1px solid #ccc;
box-sizing: border-box;
white-space: pre-wrap;
word-wrap: break-word;
}
/* textarea */
textarea { color: transparent; caret-color: black; }
textarea::selection { background: #ff06; }
/* mirror */
#mirror {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
overflow: hidden;
z-index: 1000;
border-color: transparent;
}
#mirror mark {
background: transparent;
color: red;
}
Let's highlight a few things there.
- As mentioned, our container will have relative positioning, so that the field and mirror within it can be positioned absolutely, relative to it, and occupy the same space
- The field and mirror have the same key styles; this is key so that the mirror accurately reflects the same text flow and sizing as the field
- We set the field text to transparent - but this hides the caret too, hence we need to explicitly set
caret-color
- We'll use native
mark
elements to highlight bits of text in the mirror; these will have a different text colour from the rest of the mirror
That just leaves our JavaScript:
//elements
const ta = document.querySelector('textarea');
const mirror = document.querySelector('#mirror');
//main function
const callback = () => {
mirror.innerHTML = ta.value.replace(
/^\/\/[^\n]+$/mg,
'<mark>$&</mark>'
);
mirror.scrollTop = ta.scrollTop;
mirror.scrollLeft = ta.scrollLeft;
};
//call the function on field input and scroll
ta.addEventListener('input', callback);
ta.addEventListener('scroll', callback);
In my case, I'm using the function as a rudimentary code syntax highlighter, changing the colour of comments, i.e. lines beginning //...
. Of course, I'd need to extend this to cover multi-line comments, variables, if-conditions, etc., but you get the idea.
Our function is called any time the field receives input or is scrolled, so the mirror can update (and keep its scroll position in sync.)
Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!