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
This browser doesn't support the CSS Custom Highlight API yet, so the live demos below can't
paint. The library itself degrades the same way: mark() quietly does nothing
and your page keeps working.
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 — signposts, 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.