Skip to content
KONIGI

AI Assistants / Grounding and disclosure / Citation chip

1 of 5

Citation chip

A well-sourced paragraph and a fabricated one read identically until the viewer can see where each claim came from.

Updated September 12, 2026

Problem

Two sentences sit next to each other in the same paragraph, in the same voice, with the same confidence. One is drawn from a document the system retrieved a minute ago. The other was produced by a language model with no source behind it. Nothing in the typography distinguishes them.

Solution

Attach the marker to the claim rather than to the answer. That granularity is the entire value of the pattern. A list of eight sources under a response establishes that eight things were consulted and leaves the reader to guess which sentence came from which, or whether any particular sentence came from any of them. A chip at the end of a clause binds one assertion to one source, which is the only form that helps a reader check the assertion they actually doubt.

The common forms are a superscript number, a small pill carrying the source’s favicon, and an underline that reveals a card on hover or focus. The pill carrying a domain is the strongest of the three for general use, because the domain is the fastest trust signal a reader has and it works before any interaction. On interaction, a chip owes the reader four things: the title, the domain, the date of the source, and ideally the passage the claim was drawn from. Date is the most frequently omitted and the most often decisive, since a well-sourced answer built on a page from 2019 is a different object than the same answer built on last month’s.

The honesty problem sits underneath all of this. In a retrieval system the retrieval is real, but the attribution of a particular generated sentence to a particular retrieved chunk is frequently reconstructed after the fact rather than traced. A chip can point at a page that doesn’t contain the claim it’s attached to. That reconstruction is the limit NN/g describes: modern models are complex enough that even the engineers can’t always trace why an output appeared, so an interface that presents attribution as provenance is claiming more than the system knows.

The weakness of that link matters, because a chip earns trust whether or not it’s accurate. A reader who sees citations reads the surrounding prose as verified, and mostly doesn’t click. A decorative citation is therefore worse than no citation, because it converts an unverified claim into one the reader has stopped checking. Any product shipping this pattern owes an evaluation of how often the chip points at a page that supports the sentence.

Density is the last decision. One chip per sentence is unreadable. Clustering at the end of a clause or a sentence, with several chips collapsing into a count once there are more than three, keeps the prose readable.

Use when

The system retrieves from documents or the web and the answer makes checkable factual claims.

Don’t use when

The output is the model’s own composition: a draft, a summary of text the viewer supplied, a rewrite, a piece of code. Citing sources for a generated first draft implies an authority the output doesn’t have.

Trade-offs

Chips interrupt reading rhythm, and the denser the sourcing the worse the prose reads, so accuracy and readability pull against each other directly. They also raise trust faster than they raise verification, which is a net loss if the underlying attribution is weak. Favicons make the source recognisable and turn the answer into a strip of logos. A chip pointing at a paywalled or permission-gated page is worse than no chip at all. The reader can see that verification exists and can’t reach it.

Checklist

  • Is the marker attached to a claim or to the whole response?
  • How often does the cited page actually support the sentence, and is that measured?
  • Does the chip expose the source’s date?
  • What does a reader see on hover, on focus, and on a touch device?
  • What happens when three sentences in a row share one source?
  • Is a paywalled or permission-restricted source marked before the reader clicks?
  • Do chips survive copying the answer into another document?
  • Can a reader tell which parts of an answer have no source at all?
  • Does a dead link fail visibly or silently?
  • Are chips reachable and announced in a sensible order by a screen reader?

Compare

Perplexity puts numbered chips inline and resolves the source set before the prose begins, so the citation a sentence points at already exists when the sentence arrives. ChatGPT surfaces sources when a search tool ran and leaves unsourced prose unmarked. A chip means something there, instead of being ambient decoration. Gemini offers a separate verification pass against search rather than treating inline attribution as complete, which concedes that the chip and the check are different operations. Microsoft Copilot inside a tenant cites internal documents, where the hard part is that a cited file may be one the reader has no permission to open.

Source list is the same evidence aggregated, and answers a different question. Reasoning disclosure explains the path rather than the origin. Knowledge cutoff notice covers claims with no source because none was consulted. Tool call trace is the record of the retrieval that produced these sources. Refusal is the alternative when nothing adequate was found.

Citation chip anatomy A paragraph in which two sentences carry source chips and one carries none, with an expanded card showing the domain, the date and the passage, and an unmarked sentence flagged as the one nothing supports. Attached to the claim, not to the answer w3.org mdn.org no source Using server-sent events developer.mozilla.org · March 2026 the chip earns trust whether or not the page supports the sentence 1 3 2 4 1 ONE CLAIM, ONE SOURCE A list under the answer leaves the reader to guess which sentence came from where. 2 DOMAIN AND DATE Domain is the fastest trust signal. Date is the most omitted and often decisive. 3 THE UNMARKED SENTENCE Chips only mean something if a reader can see which parts have nothing behind them. 4 TRUST WITHOUT CHECKING Readers see citations and stop verifying, so a decorative chip is worse than none. Measure how often the cited page actually supports the sentence. That number is the pattern.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

A source attached to the claim rather than to the whole answer. The chip earns trust whether or not the page behind it supports the sentence, which is why the expanded card has to show the passage and the date.

shadcn
npx shadcn@latest add hover-card badge
npm
lucide-react
Tokens
--card--card-foreground--popover--popover-foreground--muted-foreground--border--status-warn--chart-1
Server-sent events keep one HTTP response open and push text down it as the build writes.
w3.org
Each message is a block of data: lines ended by a blank line, and the browser's EventSource object hands it to a listener and reconnects on its own if the connection drops.
mdn.org

Using server-sent events

developer.mozilla.org · March 2026

The event stream is a simple stream of text data which must be encoded using UTF-8. Messages in the event stream are separated by a pair of newline characters.

For a build log this is cheaper than polling and simpler than a WebSocket.no source

CitationChip.tsxA chip on the clause it belongs to, a card with the domain, the date and the passage, and a mark on the sentence that has none.

import { Lock } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";

/**
 * A source is four things, and the date is one of them. A chip that cannot
 * say when its page was written is pointing at something the reader cannot
 * weigh, so `date` is required rather than an optional line in the card.
 */
export type Source = {
  title: string;
  /** What the chip shows. Short, because it sits inside a sentence. */
  site: string;
  /** The full host, shown in the card. Domain is the fastest trust signal. */
  domain: string;
  /** As the source states it: "March 2026", "12 May 2024". */
  date: string;
  url: string;
  /** The passage the claim was drawn from. Missing means the attribution was reconstructed, and the card says so. */
  passage?: string;
  /** Paywalled or permission-restricted. Marked before the reader clicks, since a chip they cannot open is worse than none. */
  gated?: boolean;
};

/** One sentence and what stands behind it. `null` is a real value: it means the
 *  sentence is the model's own, and the paragraph shows that rather than hiding it. */
export type Claim = { text: string; source: Source | null };

export function CitationChip({ source, defaultOpen = false, onOpen }: {
  source: Source;
  /** Start with the card showing, as in a demo. */
  defaultOpen?: boolean;
  /** Fires when the card opens. Count these against clicks through: it is the number this pattern is measured by. */
  onOpen?: (source: Source) => void;
}) {
  return (
    <HoverCard defaultOpen={defaultOpen} openDelay={150} closeDelay={200} onOpenChange={(o) => o && onOpen?.(source)}>
      <HoverCardTrigger asChild>
        <a href={source.url} target="_blank" rel="noreferrer" className="ml-1.5 inline-flex align-baseline no-underline">
          <Badge variant="outline" className="h-4 gap-1 rounded-full px-1.5 py-0 text-[10px] font-normal text-chart-1 border-chart-1">
            {source.gated && <Lock className="size-2.5" aria-label="sign-in required" />}
            {source.site}
          </Badge>
        </a>
      </HoverCardTrigger>
      <HoverCardContent side="bottom" align="end" className="w-72 p-3">
        <p className="text-sm text-card-foreground">{source.title}</p>
        <p className="mt-1 text-[11px] text-muted-foreground">{source.domain} · {source.date}</p>
        <p className="mt-2.5 border-t pt-2.5 text-xs leading-relaxed text-muted-foreground">
          {source.passage ?? "No passage recorded. The link was matched to this sentence after the answer was written."}
        </p>
      </HoverCardContent>
    </HoverCard>
  );
}

/**
 * The paragraph, with the chip on the clause it belongs to. A sentence with
 * nothing behind it is marked, because chips only mean something when the
 * reader can see which parts have none.
 */
export function CitedParagraph({ claims, openSource, onOpen }: {
  claims: Claim[];
  /** Which source's card starts open, by url. */
  openSource?: string;
  onOpen?: (source: Source) => void;
}) {
  // A div, not a p: the chip is a hover card whose content holds paragraphs,
  // and a p cannot contain them.
  return (
    <div className="text-sm leading-7 text-card-foreground">
      {claims.map((c, i) => (
        <span key={i} className={c.source ? undefined : "decoration-status-warn/60 underline decoration-dotted underline-offset-4"}>
          {c.text}
          {c.source
            ? <CitationChip source={c.source} defaultOpen={c.source.url === openSource} onOpen={onOpen} />
            : <span className="ml-1.5 text-[10px] text-status-warn">no source</span>}
          {i < claims.length - 1 && " "}
        </span>
      ))}
    </div>
  );
}

demo.tsxHow it is called: three claims, two sourced, the MDN card open.

import { CitedParagraph, type Source } from "./CitationChip";

/**
 * Three sentences about the slow build's log stream. Two are drawn from pages
 * the assistant retrieved and carry a chip; the third is the model's own
 * inference and is marked as such. The MDN card starts open.
 */
const W3C: Source = {
  title: "Server-Sent Events, W3C Recommendation",
  site: "w3.org",
  domain: "www.w3.org",
  date: "February 2015",
  url: "https://www.w3.org/TR/eventsource/",
  passage: "The EventSource interface is used to receive server-sent events. It connects to a server over HTTP and receives events in text/event-stream format without closing the connection.",
};

const MDN: Source = {
  title: "Using server-sent events",
  site: "mdn.org",
  domain: "developer.mozilla.org",
  date: "March 2026",
  url: "https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events",
  passage: "The event stream is a simple stream of text data which must be encoded using UTF-8. Messages in the event stream are separated by a pair of newline characters.",
};

export default function Demo() {
  return (
    <div className="rounded-lg border bg-card p-4 pb-32">
      <CitedParagraph
        openSource={MDN.url}
        onOpen={(s) => console.log("opened", s.domain)}
        claims={[
          { text: "Server-sent events keep one HTTP response open and push text down it as the build writes.", source: W3C },
          { text: "Each message is a block of data: lines ended by a blank line, and the browser's EventSource object hands it to a listener and reconnects on its own if the connection drops.", source: MDN },
          { text: "For a build log this is cheaper than polling and simpler than a WebSocket.", source: null },
        ]}
      />
    </div>
  );
}
What it renders. Identical markup in both panes, with only the token values changing.

Examples

No captures reference this pattern yet. Captures arrive product by product; see Products for what's in the gallery so far.