Fragments and Highlights

Text Fragments and Custom Highlights

A text fragment links to a phrase that has no id. Append #:~:text=[prefix-,]textStart[,textEnd][,-suffix] to any URL and the browser finds the first match, scrolls to it and highlights it; prefix- and -suffix are context words that are matched but not highlighted, so a phrase occurring twice can still be pinpointed. The :~: directive is stripped before scripts see the URL, and it runs only on user-initiated navigations of the top-level document. Feature-detect with document.fragmentDirective, restyle the match with ::target-text, opt a site out with Document-Policy: force-load-at-top.

Building a text-fragment link and what Chrome reported after following itJavaScript
const enc = encodeURIComponent;
const textLink = (url, start, suffix) =>
  `${url}#:~:text=${enc(start)}` + (suffix ? `,-${enc(suffix)}` : '');
console.log(textLink('http://localhost:8000/notes.html', 'renderer builds', 'a layout'));
// then, in the page Chrome 152 loaded from that link:
console.log(String(document.fragmentDirective), JSON.stringify(location.hash), Math.round(scrollY));
Output
http://localhost:8000/notes.html#:~:text=renderer%20builds,-a%20layout
[object FragmentDirective] "" 820

Scroll-to-text is Baseline since Firefox 131 555 (Chrome 80 1 , Safari 16.1 10 ); ::target-text needs Safari 18.2.

For matches you compute yourself — search hits, spell-checking, a collaborator's cursor — use the CSS Custom Highlight API instead: CSS.highlights.set('hit', new Highlight(...ranges)) paints Range objects through ::highlight(hit) without inserting <mark> elements (Range and Selection). highlight.priority settles overlaps, only color, background-color, text-decoration, text-shadow and -webkit-text-stroke apply, and ranges store node offsets, so recompute them when the text changes. Chrome 105, Safari 17.2, Firefox 149.