javascript 39 lines · 7 steps

Safely highlighting search matches in text

Wrap query terms in <mark> tags while HTML-escaping everything, so highlighting never introduces an injection.

Explained by highlit
1function escapeHtml(str) {
2 return str.replace(/[&<>"']/g, (ch) => ({
3 '&': '&amp;',
4 '<': '&lt;',
5 '>': '&gt;',
6 '"': '&quot;',
7 "'": '&#39;',
8 }[ch]));
9}
10 
11function escapeRegExp(str) {
12 return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
13}
14 
15export function highlightMatches(text, query) {
16 const terms = query
17 .trim()
18 .split(/\s+/)
19 .filter(Boolean)
20 .map(escapeRegExp);
21 
22 if (terms.length === 0) {
23 return escapeHtml(text);
24 }
25 
26 const pattern = new RegExp(`(${terms.join('|')})`, 'gi');
27 let result = '';
28 let lastIndex = 0;
29 
30 for (const match of text.matchAll(pattern)) {
31 const start = match.index;
32 result += escapeHtml(text.slice(lastIndex, start));
33 result += `<mark>${escapeHtml(match[0])}</mark>`;
34 lastIndex = start + match[0].length;
35 }
36 
37 result += escapeHtml(text.slice(lastIndex));
38 return result;
39}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Escape user-controlled text before building HTML to avoid injecting markup.
  2. 2Escape query terms for regex so special characters are matched literally, not interpreted.
  3. 3Interleaving escaped plain text with escaped matches lets you inject safe markup at precise boundaries.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Safely highlighting search matches in text — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code