highbeam

Highlight anything on the page — without touching the DOM.

A 2.4 kB, framework-agnostic text marker built on the CSS Custom Highlight API. No spans injected, no framework fights, styled with plain CSS.

npm install highbeam

 

On high beams, briefly

A high beam is the far-reaching setting of a vehicle's headlamp: a beam of light thrown long and wide down an unlit road. Where the low beam politely illuminates the next few meters, the high beam finds everything — sign­posts, stray deer, the road's vanishing point.

Text on a page deserves the same treatment. When a reader searches, the matches should light up at once — every occurrence, even a word split across element boundaries, even a phrase wrapped onto a new line. The headlamp does not rebuild the road to light it, and a highlighter should not rebuild your DOM to paint it.

That is the whole idea here: point the beam of light, sweep it across the text, and leave the markup exactly — exactly — as it was.

Why not just wrap matches in spans?

Span injection mutates DOM that your framework believes it owns. React reconciles against nodes that no longer exist; highlights vanish on the next render, or the app throws. highbeam paints through CSS.highlights instead — the DOM is read, never written.

“Why not just re-run mark.js after every render?” Without an unmark() first it progressively nests <mark> tags around already-wrapped text; with one, every render churns the DOM React owns and still flashes unstyled between frames. Re-painting ranges has neither problem — there's nothing to un-inject.

Set it and forget it

live: true watches the DOM and re-marks automatically — new messages below keep lighting up with zero extra code. Watching the page is loop-proof only because highbeam never writes to it; a span-injecting highlighter would trigger itself.

new Highbeam(logEl, { live: true }).mark(term);
// that's the entire integration

Use it

// find and paint — returns the match count
import { Highbeam } from 'highbeam';

const beam = new Highbeam(document.body, { name: 'search' });
beam.mark('beam of light');   // string, array, or RegExp
beam.clear();
/* style it with plain CSS */
::highlight(search) {
  background-color: #fff067;
  color: #16130e;
}

Works in every modern engine — Chrome 105+, Edge 105+, Safari 17.2+, Firefox 140+. In older browsers mark() quietly does nothing and your page keeps working: check isSupported() if you want to offer a fallback.