Inline citation
Markers inside an answer that show where a claim came from: numbers, superscripts, a site chip, the quoted passage itself, or notes in the margin of a long report.
Loading preview…
Install
- 1
Run this command in your Tailwind CSS project. It adds
components/ai/inline-citation-pill.tsxand installs its dependencies.$ npx shadcn@latest add https://gradientdeck.com/ai-components/r/inline-citation-pill.json
Add citations and source cards to an AI answer →
Usage & API
Example
import { CitedAnswer } from "@/components/ai/inline-citation-pill"
<CitedAnswer
parts={[]}
sources={[]}
/>Props CitedAnswer
| Prop | Type & description | Required |
|---|---|---|
parts | AnswerPart[] | Required |
sources | CitationSource[] | Required |
defaultOpen | numberOpen the card for this citation (0-based, counting citations only) on mount. | Optional |
Dependencies
- React
- Tailwind CSS
- motion
Implementation notes
Pill exports CitedAnswer with parts, sources, and defaultOpen. Each citation is a round button, at least 18px tall, showing the cite number. The accessible name is Source {n}: {site}. The card is a tooltip, at most 296px wide, placed from the marker's rectangle. It shows a letter favicon, the site, the domain, a two-line title, and a three-line snippet. Moving the pointer onto the card cancels the close timer, which is 140ms. There is no footnote list under the paragraph. The card opened by defaultOpen is dismissed on the first pointer enter, so it does not stick to the demo.
Example
import { SuperscriptAnswer } from "@/components/ai/inline-citation-superscript"
<SuperscriptAnswer
parts={[]}
sources={[]}
/>Props SuperscriptAnswer
| Prop | Type & description | Required |
|---|---|---|
parts | AnswerPart[] | Required |
sources | CitationSource[] | Required |
defaultOpen | numberOpen the card for this citation (0-based, counting citations only) on mount. | Optional |
Dependencies
- React
- Tailwind CSS
- motion
Implementation notes
Superscript exports SuperscriptAnswer with the same props as the pill: parts, sources, and defaultOpen. The marker is a raised number in a sky colour, not a pill, with the same Source {n} name and the same card. A ResizeObserver replaces the card when the text column changes width. Under the paragraph, an ordered list shows every source: number, favicon, title, and the domain once the container is wide enough. The row whose source is open uses a stronger text colour. Escape still closes the card. cite remains 1-based, so sources[0] is marker 1.
Example
import { SourceChipAnswer } from "@/components/ai/inline-citation-chip"
<SourceChipAnswer
paragraphs={[]}
sources={[]}
/>Props SourceChipAnswer
| Prop | Type & description | Required |
|---|---|---|
paragraphs | ChipPart[][]One array of parts per paragraph. | Required |
sources | ChipSource[] | Required |
defaultOpen | numberOpen this chip (0-based, counting chips only) on mount. | Optional |
className | stringExtra classes for the root, which is also the area the card stays inside. | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Source chip exports SourceChipAnswer. paragraphs is an array of paragraphs, each an array of strings or { sources: number[] }. Other props are sources, defaultOpen, and className. A source has domain, site, title, date, snippet, url, and color. The marker keeps the last word of the previous text with it, so the chip does not start a line. It shows the first domain, a letter tile, and +n when more sources are attached. The card is a link to the current url. Previous and Next buttons, and ArrowLeft and ArrowRight on the chip, move page, with a counter such as 1/3. Escape closes the card and returns focus to the chip.
Example
import { QuotedAnswer } from "@/components/ai/inline-citation-quote"
<QuotedAnswer
paragraphs={[]}
sources={[]}
/>Props QuotedAnswer
| Prop | Type & description | Required |
|---|---|---|
paragraphs | QuotePart[][]One array of parts per paragraph. | Required |
sources | QuoteSource[] | Required |
defaultOpen | numberOpen this claim (0-based, counting claims only) on mount. | Optional |
showSummary | booleanShown under the answer: how many claims were checked. | Optional |
className | stringExtra classes for the root, which is also the area the card stays inside. | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Quoted span exports QuotedAnswer. paragraphs is an array of paragraphs made of strings or claims. A claim has text, source as an index into sources, passage, match phrases to highlight, an optional support of full or partial, and an optional note. Props also include defaultOpen, showSummary, default true, and className. The claim is a dotted underline. Partial support is amber, and its accessible name says partly supported. Hover opens the card. Click pins it, and a click outside or Escape closes it. The card quotes the passage with mark highlights, shows Partly supported plus the note when support is partial, and ends with an Open source link. Enter on a claim moves focus to that link. The summary line counts claims and distinct sources, and a button focuses the first partial claim.
Example
import { MarginNotesDoc } from "@/components/ai/inline-citation-margin"
<MarginNotesDoc
title="…"
meta="…"
sections={[]}
notes={[]}
/>Props MarginNotesDoc
| Prop | Type & description | Required |
|---|---|---|
title | string | Required |
dek | stringShort line under the title. | Optional |
byline | stringByline, e.g. author and date. | Optional |
meta | stringShown in the top bar next to the title. | Required |
sections | MarginSection[] | Required |
notes | MarginSource[] | Required |
defaultPinned | number | nullStart with this note pinned (0-based). | Optional |
onShare | () => void | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Margin notes exports MarginNotesDoc. Props are title, optional dek and byline, meta, sections, notes, defaultPinned, and onShare. A section has an optional heading and paragraphs of strings or { text, note }. note is the index into notes. Each note has site, domain, title, excerpt, and color. The marker is a small number. Hover highlights the sentence. Click pins the note, sets aria-pressed, and Escape clears the pin. At a container width of 900px the notes sit in a 272px rail and shift so they do not overlap, with a curve back to the marker. Below that width the same notes are footnotes under the paragraph, and the excerpt shows only while pinned. Copy writes the title, dek, headings, and paragraph text. Share calls onShare and shows Link copied. The sample is a research brief that cites a £7,500 boiler grant.
Choosing a layout
Use a pill or a superscript when every citation is one source and a short card is enough. The superscript also prints a numbered list under the paragraph, and the row for the open source is emphasised, so it suits an answer that should keep a visible bibliography. Use a source chip when one clause rests on two or three domains and you want the domain in the marker, not a bare number. Use a quoted span when the reader needs the passage, and a warning when the source only backs part of the sentence. Use margin notes for a report the person will read and pin, not for a one-paragraph chat reply.
Do not use these to list every page the model opened. A chip that says reuters.com is a citation. A button that says 12 sources, a carousel, or a side panel is Sources list. A card for a URL the person pasted, or a hover card on an ordinary link, is Link preview. Keep the marker next to the claim. If you only have a list of URLs and no place to attach them in the prose, you do not have an inline citation yet.
Are the citation numbers zero-based?
No. On the pill and the superscript, cite is 1-based: the button shows cite and the card reads sources[cite - 1]. On margin notes, note is 0-based: the first note is notes[0], and the marker prints note + 1. On the source chip, the numbers inside sources are indexes into the sources array, also 0-based, and the chip shows a domain rather than those indexes. On the quoted span, claim.source is a 0-based index too. defaultOpen, where it exists, counts markers in reading order, starting at 0, not the cite value.
How does a quoted span say the source only partly agrees?
Set support to partial and pass note. The claim underline turns amber, and the accessible name includes partly supported by the site. The card adds a line that starts Partly supported and then the note. If showSummary is left at its default, true, the footer counts how many claims are partial and a button moves focus to the first one, then pins it. match is separate: those phrases are wrapped in mark inside passage, whether support is full or partial. Clicking the claim pins the card so the reader can follow Open source.