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 105Edge 105Safari 17.2Firefox 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.