Custom Highlight API (::highlight()) + ::target-text
Custom highlights Baseline 2025 (Chrome 105, Safari 17.2, Firefox 140). `::target-text` Baseline 2025 (Chrome 89, Safari 18.2, Firefox 131).
Chrome 105
Edge 105
Safari 17.2
Firefox 140
Style arbitrary text ranges (search matches, spellcheck, code tokens) without wrapping them in spans, via JS `Highlight` objects and CSS `::highlight(name)`. `::target-text` styles the text a scroll-to-text-fragment URL (`#:~:text=`) jumped to.
Type-safe highlights work without changing the DOM. Highlights are cheap and fast.
CSS
.fx-026 {
font-family: system-ui
}
::highlight(fx-026-match) {
background: #fde68a;
color: #111;
text-decoration: underline wavy #d97706
}
::target-text {
background: #bfdbfe
}
HTML
<p class="fx-026">Type-safe highlights work without changing the DOM. Highlights are cheap and fast.</p>
<script>
const p=document.querySelector('.fx-026');const t=p.firstChild;const ranges=[];
const re=/highlights?/gi;let m;while((m=re.exec(t.data))){const r=new Range();r.setStart(t,m.index);r.setEnd(t,m.index+m[0].length);ranges.push(r)}
if(window.Highlight){CSS.highlights.set('fx-026-match',new Highlight(...ranges))}
</script>Fallback: Feature-detect `window.Highlight`; fall back to wrapping matches in `<mark>` if you must support older engines.
Source: developer.mozilla.org/en-US/docs/Web/CSS/Guides/Custom_highlight_API
Prompt
For search-result or syntax highlighting, do not inject `<span>` wrappers. Build `Range` objects for the matches, register `CSS.highlights.set('name', new Highlight(...ranges))`, and style with `::highlight(name)` (only color, background, text-decoration and text-shadow are allowed). Also style `::target-text` so text-fragment deep links show where they landed.