Highlight text without touching the DOM.
Zero DOM mutation. Uses the Custom Highlight API — highlights are a rendering layer, not DOM nodes. No wrappers, no broken selectors, no killed event handlers.
Full CSS control. Background color, text color, underlines, text shadows, custom animations — anything CSS ::highlight() supports.
Live. Highlights follow DOM changes automatically. Content updates? Highlights re-apply on their own.
import { search } from '@lorb/ghost-highlight';
search('keyword');
// Every instance of "keyword" on the page is now highlighted.
// The DOM is untouched.npm install @lorb/ghost-highlightimport { search, clear } from '@lorb/ghost-highlight';
search('error'); // highlight all matches
search('warning', { // with custom color
style: { backgroundColor: 'rgba(255, 200, 0, 0.3)' },
});
clear(); // remove all highlightsBeyond background color — use text color, underlines, text shadows, and decorations.
search('deprecated', {
style: {
backgroundColor: 'rgba(255, 0, 0, 0.1)',
color: '#ff4444',
textDecoration: 'line-through red',
},
});
search('important', {
style: {
textShadow: '0 0 8px gold',
backgroundColor: 'rgba(255, 215, 0, 0.2)',
},
});Three built-in animation presets, or define your own keyframes.
// Built-in presets: fade-in, pulse, sweep
search('new', {
style: {
backgroundColor: 'gold',
animation: { preset: 'pulse', iterations: 3 },
},
});
// Custom keyframes — anything CSS animation supports
search('alert', {
style: {
animation: {
keyframes: '@keyframes glow { 0% { background-color: transparent } 50% { background-color: red } 100% { background-color: transparent } }',
duration: '1s',
easing: 'ease-in-out',
iterations: 'infinite',
},
},
});Different groups stack visually. Priority controls which renders on top.
import { search, createGroup, clear } from '@lorb/ghost-highlight';
// Built-in presets with priority levels
search('error message', { preset: 'error' }); // priority 2 (top)
search('network', { preset: 'warning' }); // priority 1
search('response', { preset: 'info' }); // priority 0 (bottom)
clear('error'); // clear one group
clear(); // clear allFor precise control — highlight exact positions, not just text matches.
import { highlight } from '@lorb/ghost-highlight';
const range = new Range();
range.setStart(textNode, 10);
range.setEnd(textNode, 25);
highlight(range, 'selection', {
backgroundColor: 'rgba(0, 120, 255, 0.3)',
});Highlights automatically re-apply when the DOM mutates.
import { search, observe } from '@lorb/ghost-highlight';
observe(); // start watching
search('keyword'); // highlights persist through content updates
// Dynamic content? SPA navigation? Editor changes?
// Highlights follow automatically.import { getGroupsAt } from '@lorb/ghost-highlight';
const groups = getGroupsAt(textNode, 5);
// Returns array of group names at that cursor position
// Useful for context menus, inspectors, or tooltipsUses the Custom Highlight API — zero DOM mutation. Supported in Chrome 105+, Edge 105+, Safari 17.2+, Firefox 132+.
In older browsers, falls back to <span> wrapping (DOM mutation, with a console warning). Use isSupported() to check which mode is active.
| Export | Description |
|---|---|
search(query, options?) |
Highlight all text matches. Returns match count |
highlight(range, group?, style?) |
Highlight a specific Range |
clear(group?) |
Clear one or all groups |
createGroup(name, options?) |
Pre-configure a highlight group with styles and priority |
observe(root?, debounceMs?) |
Auto-update highlights on DOM changes |
getGroupsAt(node, offset) |
Query which groups exist at a position |
isSupported() |
Check for Custom Highlight API support |
Built-in presets: search, error, warning, info, selection
Animation presets: fade-in, pulse, sweep
𖦹 MIT — Lorb.studio