Citations

Free · MIT

Inline numbered source chips that open a spring hover card with title, domain, snippet and date, a CitedText helper that turns [1] markers into chips, and a sources list whose row lights up when its chip is hovered.

MarkdownOpen in v0
Open in v0

Installation

$ npx shadcn@latest add https://goodui.dev/r/citations.json

The file lands in components/goodui/citations.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/citations works too.

Usage

import { Citations, CitedText, SourceList } from "@/components/goodui/citations"

<Citations sources={[
  { n: 1, title: "Deposits and no-show fees", url: "https://help.example.com/deposits", snippet: "New clients pay a 20% deposit…", date: "Oct 2026" },
  { n: 2, title: "No-show report, September–October", url: "https://reports.example.com/no-shows", date: "Nov 1, 2026" },
]}>
  <p><CitedText text="No-shows fell from 9 to 3 [2] after the deposit rule [1]." /></p>
  <SourceList limit={3} />
</Citations>

When to use

Use it for
  • RAG or search-grounded answers where each claim points at a document, page or record.
  • Answers that quote internal docs, policies or reports: the card shows the snippet without leaving the chat.
  • A sources list under the answer, with the matching row lighting up when a chip is hovered.
Reach for something else
  • Academic footnotes in long documents: a footnote list at the end of the page reads better.
  • A single link: a plain anchor.
If you needUse
Model output with [1] markers<CitedText text={answer} />
Hand-placed chips in JSX<Cite n={2} /> anywhere inside <Citations>
Card below the chipside="bottom" (it flips automatically near the top of the viewport)
Short list with Show all<SourceList limit={3} />

Examples

Chips in your own markup

<p>Deposits are refundable for 24 hours<Cite n={1} />, and reminders go out twice<Cite n={3} />.</p>

Favicon per source

{ n: 1, title: "Stripe docs", url: "https://docs.stripe.com/payments", icon: <img src="/favicons/stripe.png" alt="" /> }

API reference

Citations

PropTypeDefault
sources*CitationSource[]—
labels
Text for built-in controls and screen readers; pass translations here.
Partial<CitationsLabels>—

Also accepts every <div> attribute.

Cite

PropTypeDefault
n*
Source number, matching a source's `n`.
number—
side
Where the hover card opens.
"top" | "bottom""top"

Also accepts every <element> attribute.

CitedText

PropTypeDefault
text*
Text with "[1]" style markers.
string—

Also accepts every <span> attribute.

SourceList

PropTypeDefault
limit
Rows shown before a "Show all" button.
number3
hideTitle
Hide the "Sources" heading.
booleanfalse

Also accepts every <section> attribute.

Accessibility

TabMoves between chips; focusing a chip opens its card.
EnterOpens the source URL in a new tab (chips with a URL are links); on a chip without a URL, Enter or Space opens the card.
EscapeCloses the card.
  • Each chip is a link (new tab, rel="noreferrer") when the source has a URL, otherwise a <button>; both are labelled "Source n: title" and described by the open card (role="tooltip") through aria-describedby.
  • The card opens on hover (mouse only) and focus, and closes on blur, Escape, scroll or resize.
  • The sources list is an ordered list; rows with a URL are links named by their title, domain and date, with an "Open source" tooltip.
  • Built-in strings go through the labels prop for translation.

Motion

  • The card scales from 0.94 at the chip's edge on a spring (stiffness 560, damping 32) and fades out in 120 ms; it is positioned fixed and clamped to the viewport.
  • Source rows spring into place when the list expands; the hovered chip's row and number tile change color.
  • Reduced motion: the card and rows fade only.

Notes for AI

Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.

  • Wrap the answer and the list in <Citations sources>. Source numbers (n) are what CitedText matches: [2] renders source n=2.
  • CitedText only understands [n] markers. Convert other formats (【1】, ^1) before passing the text.
  • A chip whose number has no matching source renders nothing.
  • domain is derived from url when omitted; snippet, date and icon are optional.

FAQ

Why is the card positioned fixed?

So it can escape chat containers with overflow hidden or scrolling. It closes on scroll instead of following the chip.

Why is a chip sometimes a link and sometimes a button?

With a URL it is a real link, so middle-click, copy link and screen readers' link lists work. Without one it is a button that only opens the card.

Related