Skip to content

Conversation

Source List

Citations behind an answer — numbered footnotes with hover previews and a panel of every source used.

2 variantsMIT

Preview

Hashed build files can be cached for a year with immutable because their names change on every deploy . Cloudflare caches them by extension out of the box , and a _headers file sets the long Cache-Control for /_next/static/* . HTML stays uncached so new deploys show up at once .

  1. 1Caching static assets at the edge
  2. 2Static exports
  3. 3Cache-Control: immutable
  4. 4Custom headers for static assets

Installation

Copies the component into components/ui and installs its dependencies and theme tokens.

pnpm dlx shadcn@latest add https://tuffui.com/r/source-list.json

Usage

Pass your own data and wire the callbacks to your agent runtime.

import { SourceList } from "@/components/ui/source-list";

// The model writes markers like [1] or [2, 3] that index into the sources it used.
export function CitedAnswer({ answer }: { answer: { text: string; sources: Source[] } }) {
  return <SourceList data={{ text: answer.text, sources: answer.sources }} />;
}

export function SourcesTab({ answer }: { answer: Answer }) {
  return (
    <SourceList
      variant="panel"
      data={{ sources: answer.sources, claims: answer.claims }}
      onOpen={(source) => analytics.track("source_opened", { url: source.url })}
    />
  );
}

Props

PropTypeDefaultDescription
data*SourceListData—Sources in citation order, plus the answer text with [n] markers (footnotes) or the claims each source supports (panel).
variant"footnotes" | "panel""footnotes"Inline markers with hover previews and a numbered list, or a panel grouped by site showing which claims each source backs.
onOpen(source: Source) => void—Called when a source link is opened.
classNamestring—Extra classes for the root element.
type Source = { id: string; title: string; url: string; snippet?: string; date?: string };

type SourceListData = {
  sources: Source[];
  text?: string; // "… [1] … [2, 3]"
  claims?: { id: string; text: string; sourceIds: string[] }[];
};