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 .
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.jsonUsage
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
| Prop | Type | Default | Description |
|---|---|---|---|
| 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. |
| className | string | — | 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[] }[];
};