Inline anchors and hover previews
A citation the reader has to leave the page to check is a citation nobody checks — the anchor, the preview and the jump-to-source are one interaction, and the preview is the part that does the work.
HouseWarm already ships the smallest honest version of a citation. A field comes back from extraction, and next to it sits the crop of the document the value was read from. The reviewer does not have to trust the number. They can look.
That works because there is exactly one source, it is one image, and the crop is already in memory. Change the run to an agent that read three documents, quoted two of them, and produced six sentences, and the crop model collapses. There is no single image. There are spans of text inside documents that live somewhere else, attached to sentences by an association the runtime may or may not have recorded. What the reviewer gets instead is the industry default: a small superscript number, or a pill, that opens a new tab.
This lesson is about why that default fails, and what the anchor has to do instead.
The finding that should change your default
Nielsen Norman Group ran usability testing on AI chat interfaces and published the results in December 2025. The sentence that matters here is blunt: “people rarely click citation links.”
The illustration is better than the sentence. A participant told the researchers, in their own words, that they trusted the chatbot a lot, and that if they wanted to learn more and see where it got the information from, they could just click the source and keep reading. During the session, they clicked none.
NN/g is a consultancy that sells training on this material, so the incentive runs toward findings that make design expertise look necessary. Read the finding anyway: it is observed session behaviour, the participant quote cuts against the participant, and it matches what anyone who has watched a review queue already suspects.
The same article gives the placement rule that follows from it. Sources work best positioned directly adjacent to the specific claims they support, rather than buried in response text. Not a bibliography at the bottom. Not a sources panel to the side. Next to the sentence.
Why the new tab loses
The cost of clicking a citation is not the click. It is everything after it. A new tab means the reviewer loses the sentence they were checking, arrives at a document they now have to search, finds a page of text with no indication of which part was relevant, and then has to come back and re-find their place. Four operations, and only the first one was interesting.
Every one of those costs falls on the reviewer at the exact moment they are deciding whether the check is worth it. So the check does not happen, the reviewer approves, and the run ships with a citation nobody read.
This course’s answer is to treat the anchor, the preview and the jump-to-source as one interaction with three depths rather than one link. That framing is the course’s own. NN/g’s research supports adjacency and reports the low click rate; it does not test or recommend a hover preview, and nothing in that article prescribes any particular popover mechanic. What follows is engineering built on their finding, not a second finding.
- The anchor sits next to the claim and says which source, in words, not as a number. A reviewer who recognises the source name has already done a cheap sanity check without any interaction at all.
- The preview shows the passage in place, without leaving the page. This is the part that does the work, because it is the only depth whose cost is low enough that a reviewer will pay it repeatedly.
- The jump opens the full source, for the one citation in twenty where the passage is not enough.
The three depths carry different obligations. The anchor is allowed to be terse. The preview is not allowed to be a summary. The jump is not allowed to be the only way through.
What has to be in the payload before you can draw any of it
Same rule as the first module, applied to a new component: the preview is a function of what the runtime recorded, and most runtimes record less than the design needs. Before drawing the popover, get the field list. A preview that can actually be trusted needs four things.
| What the preview needs | What happens without it |
|---|---|
| A source identity a human recognises — title, publisher, or filename | The anchor degrades to a bare domain, and the free sanity check disappears |
| The passage text as it was at retrieval time | You either show nothing or fetch the source now, which answers a different question |
| A locator into the source — offsets, chunk id, page | The jump lands at the top of a long document and the reviewer is back to searching |
| A retrieved-at timestamp | The preview cannot say whether it is showing a live page or a stale snapshot |
If the run only emitted a URL per citation, the preview cannot exist, and the correct component behaviour is to render an anchor with no preview affordance rather than a preview affordance that opens onto an apology. A disclosure control that reveals nothing teaches the reviewer to stop using disclosure controls.
Where people get burned
The tempting shortcut is to fetch the cited page at render time and show the top of it. Do not, or at least do not call the result a preview of the citation. Fetching now shows what the page says today. The claim you are checking rests on what the model was given, which may have been a different revision, a different section, or a cached copy. Those two things disagree quietly and in the direction that makes the system look correct.
The other shortcut is worse: generating a summary of the source to fit the popover. That inserts a second model between the claim and its evidence, and now the reviewer is verifying one generation against another. If the passage is too long for the popover, truncate it and say so. Never paraphrase it.
Hover is not the interaction
Do not re-teach yourself React here. The relevant point is narrower and it is about what the interaction has to survive.
Hover does not exist on touch. It is not reachable by keyboard. It has no meaning to a screen reader. A preview that only appears on hover is a preview that some reviewers cannot open at all, and the reviewers most likely to be doing this work in volume are the ones most likely to be driving from the keyboard. The preview needs a real trigger — focusable, activatable, with an obvious state — and hover is a convenience layered on top of it, not the mechanism.
There is a second reason to give it a click-or-key trigger. A hover preview vanishes the moment the pointer moves, which means the reviewer cannot select the passage, cannot compare it against the sentence for more than a moment, and cannot keep it open while they read. Verifying is not glancing. The preview has to stay put.
Retrieval check
The run emits one citation per sentence, each with a URL and a retrieved-at timestamp, and nothing else. What does your anchor render?
Check your answer
The source identity you can derive from the URL, the retrieved-at date, and no preview affordance. That is the whole honest inventory.
The thing to resist is manufacturing the missing half. You could fetch the page and show its opening paragraph, and it would look exactly like a preview built from a recorded passage. It is not one. It shows the current page, chosen by position rather than by relevance, and nothing about it is connected to the sentence it sits beside. A reviewer reading that popover would reasonably conclude they had just checked the claim.
The useful move is to record what the component wanted and did not get, which is the module-one habit applied to a new surface. “No passage text emitted” is a finding about the runtime, and it belongs in ARTIFACT.md rather than being papered over in the view. It is also the sentence that gets a retrieval pipeline changed, because it names a concrete missing field rather than describing a UI you wish you could build.
Two states the anchor has to be able to say
Most citation UI has one state: cited. That is not enough for a review surface, because two of the situations a reviewer meets most often are not “cited” at all.
A sentence with no citation. In a response where most sentences carry anchors, the ones that do not read as ordinary prose rather than as uncited claims. The absence is invisible precisely because the presence is small. Whatever the component does about that, it should be a decision rather than an accident.
A citation that could not be resolved. The source id does not match anything in the retrieval record, or the locator points past the end of the document. This has to fail visibly. Silently dropping the anchor turns a broken citation into an uncited sentence, which is the one transformation guaranteed to lose information the reviewer needed.
Hands on
Specify the anchor against your own captured run
Done when: ARTIFACT.md’s “Anchor + preview” field names the four preview fields, marks each present or absent in your payload, and states in one sentence what the anchor renders when the passage text is absent.
- Go back to the payload you captured in the first module and find every place a source is referenced — a retrieval span, a tool result carrying documents, a citation array on the final message. List the fields each one actually carries.
- Mark the four fields from the table above as present or absent. Be strict about the passage: a document id is not passage text, and a chunk that was retrieved is not necessarily the chunk a given sentence rests on.
- Write the degraded rendering. One sentence per missing field, saying what the anchor shows instead. This is the specification, and it is more valuable than the popover.
- Decide and write down the trigger. Which key opens the preview, what closes it, and whether it stays open when focus moves. Hover, if you add it, is the last line of that note rather than the first.
- Write the unresolvable case: what the anchor renders when the citation points at a source the run has no record of. If your answer is “hide it,” write down why that is acceptable, because it usually is not.
- Paste the finished field into
ARTIFACT.mdand bring it into the chat. The line worth arguing about is the passage row, since most runtimes do not emit passage text and most designs assume they do.
What this does not cover
This lesson got the passage in front of the reader without making them leave the page. It said nothing about which part of that passage matters. A preview of four hundred words still hands the reviewer a search problem, only a smaller one, and the question of highlighting the exact span a sentence rests on — where that span comes from, and what you are allowed to claim about it — is the highlighting lesson, next.
It also assumed throughout that the cited source supports the sentence. That assumption has been measured, and it does not hold as often as you would hope. The unsupported-citation lesson takes that case on its own terms. And the question of how sure the system is, as opposed to where it got the text, waits for the confidence lesson at the end of this module.
Read this next — primary source
Explainable AI in Chat InterfacesMegan Chan, Nielsen Norman Group, 12 December 2025 — free to read; NN/g sells training and consulting on the practices it writes about
Read it for the observed behaviour rather than the recommendations. It is a short piece of usability research on how people actually treat AI explanations, and the single sentence this lesson is built on — that people rarely click citation links — is illustrated with a participant who said out loud that they would check the source and then did not check a single one. That gap between stated and observed behaviour is the thing you are designing around. The article also covers two topics this lesson does not: that chain-of-thought text is often unfaithful to the model’s actual computation, and how to write a disclaimer people read. Both land later in the course, and both are worth having in your head before you draw anything.
Stuck, curious, or think this lesson is wrong? Ask your teaching agent. The lessons are the scaffold; the conversation is where the learning gets unstuck.