How to Link to a Specific Sentence, Not the Whole Note
To link to a specific sentence, quote it instead of numbering it. Add #:~:text= to the end of the page URL, then the sentence itself, percent-encoded. The browser searches the page for those words and scrolls to them. The quote is the address, so the pointer is readable even after it breaks.
This is not a proposal. Navigating to a text fragment ships in every major browser engine, and has done since 1 October 2024 — Chrome 80 on 4 February 2020, Edge 80 three days later, Safari 16.1 on 24 October 2022, and Firefox 131 on the interop date itself.1 Blink, WebKit and Gecko all resolve the same URL. The idea is older than the browsers that implement it: Kevin Marks published "Fragmentions - linking to any text" on his own site on 17 April 2014;2 the W3C's Web Annotation Data Model became a Recommendation on 23 February 2017 with a selector built on the same principle;3 Chrome shipped three years after that. Six years and one Recommendation separate the coinage from the browser feature — so the first thing to unlearn is that Chrome invented it.
Quote the sentence — don't number it
One substitution: replace the identifier with the text. A block id like ^a1b2c3 is a token your app minted — and only your app understands. A quoted sentence is understood by anything that can search a document — a browser, a grep, a person reading the URL aloud. One is a private index key. The other is the passage.
Heading anchors are the rung below this one. An earlier post here on navigating a long note by its outline conceded the limit in its own words: the heading is portable, but the slug a renderer derives from it is not. A quoted anchor skips the derivation entirely.
Quoting works better than it has any right to. We measured it on this blog's own corpus — 530 finished posts, 20 sentences sampled from each, n = 10,600. A whole sentence occurred exactly once in its own document 99.34% of the time.4 Only 70 of the 10,600 sampled sentences repeated at all.
A sentence is already a near-unique address. You do not have to mint one.
Short phrases are a different animal. In the same corpus, 40.08% of 1–3-word opening phrases were ambiguous — they appeared more than once in their own document. The median number of leading words needed before a sentence's opening became unique was 2.
One honest note about that census: the 70 repeats are not random noise. Sixty-eight of them are sentences duplicated between a post's body and its own FAQ section. That is a fair picture of the real failure case, which is text you reused — but it is a result about this corpus, not about prose in general.
The five-minute version — build one link now
Here is the whole procedure. Open the page, select the sentence you want, encode it, and paste it after #:~:text=. Six steps, no plugin, no extension. The specification is explicit about the minimum: "The only required parameter is start. If only start is specified, the first instance of this exact text string is the target text."5
- Open the destination page in a browser. This is a browser behaviour, so the target must be something a browser loads.
- Copy the sentence, whole. Not three words. The whole thing — that is where the 99.34% lives.
- Percent-encode it. Spaces become
%20. The specification also requires that "Dash (-), ampersand (&), and comma (,) characters in text parameters are percent-encoded to avoid being interpreted as part of the text directive syntax" — so-becomes%2D,&becomes%26,,becomes%2C.5 - Append the directive. Take the page URL, add
#:~:text=, then the encoded sentence. - Paste it into the address bar and press Enter. Type it into the bar yourself rather than clicking from the same page — the reason is in the limits section below.
- Read the URL back. If you cannot tell what it points at by reading it, you encoded too little.
A working example, built from a sentence in an earlier post here on which link forms survive the app:
https://blog.mnmnote.com/posts/hyperlinks-in-your-notes-survive-the-app#:~:text=Treat%20any%20deep%20link%20into%20a%20sub%2Dparagraph%20as%20app%2Dspecific%20until%20you%20have%20confirmed%20otherwise
That link lands on one sentence inside a long post. The specification's own worked example does the same against a Wikipedia article, and it recommends the long form over the short one: "Prefer to specify the entire string where practical. This ensures that if the destination page is removed or changed, the intended destination can still be derived from the URL itself."5
There is a soft ceiling — "Text snippets shorter than 300 characters are encouraged to be encoded using an exact match."5 Above that, quote the start and the end instead of the whole middle.
Chrome's own developer documentation describes the delimiter plainly. "The fragment directive is a portion of the URL fragment delimited by the code sequence :~:. It's reserved for user agent instructions, such as text=, and is stripped from the URL during loading so that author scripts cannot directly interact with it," writes Thomas Steiner.6
Your page keeps its ordinary #anchor behaviour. The directive is carved out of the fragment and handled by the browser alone.
Why the same three parts keep reappearing
Three separate efforts arrived at the same shape — the quoted text, a little of what comes before it, and a little of what comes after. The W3C Recommendation of 2017 and the browser URL syntax of 2023 describe that shape in almost the same words, nearly seven years apart, and neither is a rewrite of the other.
The Web Annotation Data Model defines a TextQuoteSelector as "The class for a Selector that describes a textual segment by means of quoting it, plus passages before or after it."3 Its conformance keywords are strict about the middle and lenient about the edges — "Each TextQuoteSelector MUST have exactly 1 exact property," while prefix and suffix each "SHOULD have exactly 1 ... and MUST NOT have more than 1."3
Then it explains itself, which specifications rarely do. The selector carries the surrounding text, it says, "to distinguish between multiple copies of the same sequence of characters."3
Now read the browser side. The URL Fragment Text Directives draft defines a text directive as "a struct that consists of four strings: start, end, prefix, and suffix," of which "start is required to be non-null," adding that "The empty string is not a valid value for any of these items."5
Same anatomy. Same reason. Nearly seven years apart.
Its published grammar is #:~:text=[prefix-,]start[,end][,-suffix]. Print it as an illustration, not as law — the syntax section of that document opens by declaring itself non-normative, and the document adds that where the convenience grammar and the parsing steps disagree, "the steps there are to be taken as the authoritative source of truth."5 The four-string struct is the normative shape; the grammar is the teaching form.
Kevin Marks got there first — and for the reader's reason. "Another problem is that linking in HTML is defined to address pages as a whole, or fragments within them, but only if the fragments are marked up as an id on an element," he wrote in 2014. "But to do that I had to go and inspect the HTML and find the id, and make a link specially, by hand."2
His fix was to "use a fragment to identify part of a page" and to "mention words in the page as the identifier" — "I've named these 'fragmentions'."2
Calling this independent invention would be too neat. Marks' post opens: "A couple of weeks ago, I went to a w3c workshop about annotations on the web."2 The annotation tradition and the fragmention tradition were in the same room in 2014 — the lineage is shared, and documented in the originating post's first line.
The interesting part is not convergence in isolation. It is that one lineage kept landing on exactly three parts.
What happens when the sentence appears twice
Three efforts, one shape, three different answers. This is the question the specifications actually disagree on, and reading any one of them as "the rule" will mislead you. Ambiguity is resolved differently by the annotation model, by the browser URL, and by the original fragmention proposal — and the difference matters the moment your quoted sentence is not unique.
| Tradition | What happens when the quoted text appears more than once |
|---|---|
W3C Recommendation (TextQuoteSelector) | "If, after processing the prefix, exact, and suffix, the user agent discovers multiple matching text sequences, then the selection SHOULD be treated as matching all of the matches."3 |
WICG draft (#:~:text=) | "It returns a range that points to the first text passage within the document that matches the searched-for text and satisfies the surrounding context."5 One target, or none. |
| Fragmention (2014) | The first matching phrase. Marks' answer to ambiguity was editorial — the IndieWeb wiki summarises it as "using more words to create a unique reference, rather than trying to find a technical solution to the problem."7 |
An annotation matches all of them. A browser link picks the first one that also satisfies the context. Marks told you to write a longer quote.
All three are defensible. None of them is the others.
So when do you add the context terms? The draft's advice is narrow and worth following literally: "Use context only if one of the following is true: The UA determines the quoted text is ambiguous. The quoted text contains 3 or fewer words."5 It warns in the same breath that "their use can make the URL more brittle in some cases."5
Our census puts numbers behind that advice. Quote a full sentence and you are in the 99.34% case, where no context is needed. Quote three words and you are in the 40.08% case, where you need it about two times in five.4 The threshold in the specification and the shape of a real corpus agree.
The thirty-minute version — a pointer that outlives the browser
#:~:text= is a browser behaviour. A local Markdown editor has no obligation to implement it, and you cannot assume yours does. So for note-to-note pointers, do not rely on the URL — borrow the shape and write it into your own file, where any tool that can read text can act on it.
This is a transfer, not conformance. Nothing below claims to implement the Web Annotation Data Model — it borrows the three-part shape because the shape is good. Keep the pointer in frontmatter, or in a sidecar file, and store the same three strings the specifications store, plus enough to find the file again:
---
title: Reading notes — link durability
pointers:
- label: portability-caveat
target: notes/hyperlinks-that-survive.md
prefix: "not part of the standard Markdown format."
exact: "Treat any deep link into a sub-paragraph as app-specific until you have confirmed otherwise."
suffix: "The same applies to the embed shape"
url: "https://blog.mnmnote.com/posts/hyperlinks-in-your-notes-survive-the-app"
---
Five fields, and every one of them is legible. target says which file. exact is the passage. prefix and suffix are the few words on either side — what the annotation model uses "to distinguish between multiple copies of the same sequence of characters."3 url is the same pointer expressed as a link a browser can follow.
The payoff is what happens on failure. Rewrite the sentence and a block id leaves you a dead token; this leaves you the sentence you meant, in plain text, beside the file it was in. You can search for the surviving half of it.
That is a recoverable break — not a silent one.
There is a cheaper move if you control the destination document: quote the sentence in your own note and link to the file. The quote does the addressing; the link does the transport. That is not transclusion, which pulls the whole passage in — this points at text left in place.
Where this stops working
Five limits, worth knowing before you build a habit on this. Text directives do not cross block boundaries, do not fire on every navigation, and cannot be detected by the obvious feature test. They rot when the page is edited — and whether they work on a local file is a question this post declines to answer.
Block boundaries. The draft is explicit: "While the matching text and its prefix/suffix can span across block-boundaries, the individual parameters to these steps cannot. That is, each of prefix, start, end, and suffix will only match text within a single block."5 A quote that straddles a paragraph break will not match as one string.
Navigation type. Directives are "restricted to: top level navigables (i.e. no iframes) ... navigations that are the result of a user action."5 Steiner puts it in one sentence — "Text fragment directives are invoked only on full (non-same-page) navigations that are the result of a user activation."6 A same-page link will not jump. This is the usual reason a pasted link "does nothing".
Feature detection lies. The obvious test is if ('fragmentDirective' in document) — do not gate on it. document.fragmentDirective arrived in Chrome 86 (20 October 2020), Firefox 131 (1 October 2024) and Safari 18.4 (31 March 2025). Navigation to text fragments had worked in Safari since 16.1, in October 2022.1 For two and a half years that test returned false in Safari while the links themselves worked.
The web-features catalogue records the cause in a source comment — "document.fragmentDirective delayed by https://webkit.org/b/273466" — and deliberately computes the feature's status from navigation support instead.8
Local files. Whether #:~:text= resolves on a file:// URL is not something we tested, and the specification's restrictions do not settle it. Treat it as browser-dependent and check it in your own browser first.
Rot. The draft says it plainly: "pages on the web often update and change their content. As such, links like this may 'rot' in that the text content they point to no longer exists on the destination page." And then the sentence that justifies the whole design: "For longer duration use cases, such as references and web page links, text directives are still valuable since they degrade gracefully into an ordinary link."5
Mozilla's filed position on the proposal is worth reading for its tone. "This is an important use case to address and the proposal does a good job at mitigating the compatibility and security issues. As a result the syntax is a tad inelegant, but workable," the position states — verdict positive, filed 29 July 2019 and closed 23 December 2019.9
An engine vendor conceding the point in its own words beats any argument we could make.
Common mistakes
Six mistakes account for most broken pointers, and every one has a one-line fix. Quote too little and you land in the ambiguous 40.08%. Leave a dash unencoded and the syntax eats it. Span a paragraph break and no parameter matches. Click from the same page and nothing fires. The last two are about trusting an app-specific token to travel.
- Quoting three words instead of the sentence. The 40.08% ambiguity rate is the cost of brevity. Quote the whole sentence and the problem usually disappears.4
- Leaving a dash or comma unencoded. Both are syntax in a text directive.
-becomes%2D;,becomes%2C;&becomes%26.5 - Quoting across a paragraph break. Each parameter matches within a single block only.5
- Testing the link by clicking it from the same page. Same-page navigation does not invoke the directive.6
- Assuming an app-specific block id is a link. Obsidian's own documentation has said otherwise for years, as an earlier post here on which link forms survive the app already noted: "Block references are specific to Obsidian and not part of the standard Markdown format."10
- Assuming a block id can point anywhere in the file. The less-quoted half of that same documentation is the one that bites: "We do not support links to specific parts of quotations, callouts, and tables," and "Block identifiers can only consist of Latin letters, numbers, and dashes."10 A quoted pointer has neither restriction — it addresses a table row or a callout as readily as a paragraph, because it addresses words.
That is a vendor documenting the edges of its own mechanism in public, which is more than most do.
How this works when you own the file
A quoted pointer is portable for the same reason plain Markdown is portable: nothing outside the file has to be consulted to understand it. Open the note in any editor, in any year, and the pointer still reads as a sentence. That property is the whole argument for keeping notes as open Markdown files on your own device.
Keep the note as an open Markdown file on a device you control and the pointer inherits that property along with it. A pointer written the way this post describes travels with the file — because it is the file.
The demand has been steady and unmet for fifteen years. "Ask HN: How would you link to specific parts of a page?" was posted on 3 December 2010; when Chrome shipped the feature, "Chrome will Soon Let You Share Links to a Specific Word or Sentence on a Page" drew 361 points and 319 comments on 15 February 2019.11
On the Obsidian forum, four threads about block references have accumulated 6,668 views between October 2020 and December 2024 — the largest, "Block references and atomic notes," at 4,837 views.12
The question keeps getting asked because the answer keeps being app-specific.
Frequently asked questions
Short answers to the questions people actually type. Each one below has been asked publicly — on Hacker News, on vendor forums, or of any note app that offers a block reference. The pattern in all of them is the same: people want to point at a passage and have the pointer survive the tool that made it.
How do I link to a specific paragraph or sentence in my notes without a plugin?
Quote the sentence in the URL. Append #:~:text= to the page address followed by the percent-encoded sentence. No plugin, no extension — navigation to text fragments ships in Chrome 80+, Edge 80+, Safari 16.1+ and Firefox 131+.1 Inside a local editor, write the same three strings into your note's frontmatter instead.
Will a link to a sentence still work if I move my notes to another app?
The quoted pointer always survives — whether it resolves depends on the reader. In a browser, the URL keeps working. In a local Markdown editor, #:~:text= is a browser behaviour that no editor is obliged to implement — but the sentence is still sitting there in plain text, so a search finds it. An opaque block id gives you nothing to search for.
What happens if the sentence I linked to appears twice? It depends which tradition you are in. The W3C Recommendation says multiple matches "SHOULD be treated as matching all of the matches."3 A browser text directive returns "the first text passage within the document that matches the searched-for text and satisfies the surrounding context."5 Add prefix and suffix terms — the words immediately before and after — to disambiguate.
How would you link to specific parts of a page? Exactly the way this post describes, and the question is old — it was asked on Hacker News on 3 December 2010, nine years before Chrome shipped an answer.11 The 2010 answers were bookmarklets and per-paragraph anchors. The 2026 answer is a URL fragment your browser already understands.
Why doesn't my block reference work outside the app that made it?
Because it was never a standard. Obsidian's documentation states that "Block references are specific to Obsidian and not part of the standard Markdown format. Links containing block references won't work outside of Obsidian."10 The same category applies to other tools' block tokens — Roam's ((block-ref)) among them. The identifier is an index key, and the index does not travel.
Why did a #:~:text= fragment appear in my address bar?
Because something generated a text-fragment link for you — a browser's "copy link to highlight" command, or a search result deep-linking into a page. The :~: sequence marks the part of the fragment reserved for the browser — it "is stripped from the URL during loading so that author scripts cannot directly interact with it."6 Delete everything from #:~: onward and the plain link still works.
Does a text fragment link work on a local file:// document?
We have not tested this, so we are not going to tell you either way. The specification's restrictions on navigation do not settle it, and behaviour may differ by browser and version. Try it in the browser you actually use before you build a workflow on the answer.
Do I need to add prefix and suffix every time? No, and adding them by reflex makes links more brittle. The draft advises using context only when the quoted text is ambiguous or contains three or fewer words.5 In our corpus, 99.34% of whole sentences were already unique inside their own document, while 40.08% of 1–3-word phrases were not.4
Stop asking your app to remember which paragraph you meant, and let the paragraph say so itself. A number is a promise your tool makes to itself; a quotation is a promise the text makes to everyone. The first dies with the index. The second is still legible when the link fails — which is the only durability anything written has ever had.
This post builds on Kevin Marks' 2014 fragmention proposal, which named the idea before any browser implemented it.
If you want notes whose pointers are as portable as their prose, mnmnote.com keeps everything as open Markdown on your own device.
Footnotes
-
MDN
browser-compat-data, keyhtml.elements.a.text_fragments, joined to the release dates inbrowsers/{chrome,edge,safari,firefox}.json, plusapi.Document.fragmentDirectivefor feature-detection support. https://github.com/mdn/browser-compat-data — navigation to text fragments: Chrome 80 (2020-02-04), Edge 80 (2020-02-07), Safari 16.1 (2022-10-24), Firefox 131 (2024-10-01);document.fragmentDirective: Chrome 86 (2020-10-20), Firefox 131 (2024-10-01), Safari 18.4 (2025-03-31). Rolling dataset, no publication date. Accessed 2026-09-05. ↩ ↩2 ↩3 -
Marks, K. (17 April 2014). "Fragmentions - linking to any text." https://www.kevinmarks.com/fragmentions.html. Accessed 2026-09-05. ↩ ↩2 ↩3 ↩4
-
Web Annotation Data Model. W3C Recommendation, 23 February 2017. Editors: Robert Sanderson (J. Paul Getty Trust), Paolo Ciccarese (Massachusetts General Hospital), Benjamin Young (John Wiley & Sons). https://www.w3.org/TR/annotation-model/#text-quote-selector. Accessed 2026-09-05. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
MNMNOTE corpus census, 5 September 2026. Method: strip frontmatter, fenced code blocks and footnote definitions from each of the 530 finished posts in this blog's repository; split what remains on sentence-final punctuation and keep the sentences of 6–60 words; seed the sampler at 398; sample 20 sentences per document (n = 10,600); count occurrences of each sampled sentence, and of its first 1, 2 and 3 words, within its own stripped document. Results: 10,530/10,600 sentences occurred exactly once (99.34%); 70 occurred more than once (0.66%); median leading words to uniqueness 2 (mean 2.42); 12,745/31,800 1–3-word opening phrases were ambiguous (40.08%). Sixty-eight of the 70 repeats are body-to-FAQ duplicates, which is why the repeats are called structural in the body. The figures move by about a point if the stripping rule changes — also strip inline Markdown syntax and the same sample gives 98.68% and 38.96% — so read them as this corpus under this rule, not as constants. ↩ ↩2 ↩3 ↩4
-
Burris, N., & Bokan, D. URL Fragment Text Directives. Draft Community Group Report, 13 December 2023, Web Platform Incubator Community Group. https://wicg.github.io/scroll-to-text-fragment/ — §2.2 link lifetime, §3.2 syntax and percent-encoding, §3.4 the four-string struct, §3.5.1 navigation restrictions, §3.6.1 matching steps, §4.1 exact-match preference, §4.2 context-term advice. Accessed 2026-09-05. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16
-
Steiner, T. "Boldly link where no one has linked before: Text Fragments." web.dev. Page states "Last updated 2020-06-17 UTC." https://web.dev/articles/text-fragments. Accessed 2026-09-05. ↩ ↩2 ↩3 ↩4
-
"fragmention." IndieWeb wiki. https://indieweb.org/fragmention. Living document with no publication date; page created 17 April 2014. Accessed 2026-09-05. ↩
-
web-featurescatalogue,features/scroll-to-text-fragment.yml. https://github.com/web-platform-dx/web-features/blob/main/features/scroll-to-text-fragment.yml. Rolling dataset, no publication date. Accessed 2026-09-05. ↩ -
"Scroll To Text Fragment." Mozilla Standards Positions, issue #194. Position
positive; opened 29 July 2019, closed 23 December 2019. https://github.com/mozilla/standards-positions/issues/194. Accessed 2026-09-05. ↩ -
"Internal links." Obsidian Help. https://help.obsidian.md/links. Living document, no publication date. Accessed 2026-09-05. ↩ ↩2 ↩3
-
Hacker News. "Ask HN: How would you link to specific parts of a page?" — 3 December 2010, 8 points, 9 comments. https://news.ycombinator.com/item?id=1964608. And "Chrome will Soon Let You Share Links to a Specific Word or Sentence on a Page" — 15 February 2019, 361 points, 319 comments. https://news.ycombinator.com/item?id=19168442. Point and comment counts as of 2026-09-05. ↩ ↩2
-
Obsidian forum, block-reference threads. "Block references and atomic notes" (t/9077, 26 November 2020, 4,837 views); "Comprehensive Overview of Block References in Insiders Release 0.9.5" (t/7309, 20 October 2020, 1,448 views); "Block reference doesn't work" (t/82821, 1 June 2024, 214 views); "New plugin: Link Maintainer, help you maintain block references when splitting or reorganizing notes" (t/92862, 7 December 2024, 169 views). https://forum.obsidian.md/t/9077. View counts as of 2026-09-05. ↩