25 / Agent UI
Inline Citations
Numbered markers placed inside generated text, each linking to its entry in a source list underneath.
Postgres treats nulls as distinct inside a unique indexSource 1: PostgreSQL — Unique indexes, so the constraint itself will not reject the empty rows. The failure comes from the ordering in the migrationSource 2: 0004_add_email_index.sql, where the index is created before anything backfills the column.
Sources
- 1PostgreSQL — Unique indexes(opens in a new tab)Null values are not considered equal by a unique constraint.
- 20004_add_email_index.sqlCreates the index before the backfill statement runs.
"use client"
import {
Citation,
InlineCitations,
} from "mischief-ui/inline-citations"
const sources = [
{
id: "postgres",
title: "PostgreSQL — Unique indexes",
url: "https://www.postgresql.org/docs/current/indexes-unique.html",
snippet: "Null values are not considered equal by a unique constraint.",
},
{
id: "migration",
title: "0004_add_email_index.sql",
snippet: "Creates the index before the backfill statement runs.",
},
]
export function InlineCitationsDemo() {
return (
<InlineCitations
className="w-full max-w-xl text-[0.95rem]"
sources={sources}
>
<p className="leading-relaxed">
Postgres treats nulls as distinct inside a unique index
<Citation id="postgres" />, so the constraint itself will not reject the
empty rows. The failure comes from the ordering in the migration
<Citation id="migration" />, where the index is created before anything
backfills the column.
</p>
</InlineCitations>
)
}Installation
Copy the source into your project, or keep it behind a package.
npx shadcn@latest add Tinkerers-Labs/mischief-ui/inline-citationsimport { InlineCitations } from "mischief-ui/inline-citations"Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.
"use client" import * as React from "react"import { ArrowUpRight } from "lucide-react"import { cn } from "@/lib/utils" export type CitationSource = { id: string title: string url?: string snippet?: string} export type InlineCitationsProps = React.HTMLAttributes<HTMLDivElement> & {Usage
const sources = [
{ id: "docs", title: "Agent UI docs", url: "https://example.com/docs" },
]
export function Answer() {
return (
<InlineCitations sources={sources}>
<p>
Streaming is supported<Citation id="docs" />.
</p>
</InlineCitations>
)
}How references are numbered
Numbers come from the position of a source in the sources array, not from the order the citations appear in the text. Two mentions of the same source are therefore the same number wherever they fall, and reordering a paragraph never renumbers anything.
<InlineCitations sources={sources}>
<p>
The window is fifteen minutes <Citation id="rfc" />, and the counter
resets on success <Citation id="rfc" /> rather than on expiry{" "}
<Citation id="notes" />.
</p>
</InlineCitations>It also means the array is the thing to sort. Put the sources in the order you want them listed -- by relevance, or by the order you expect them to be met -- and the marks follow.
Writing the source list
A citation is only useful if it can be checked. Give every source a title that says what it is rather than where it lives, and a snippet holding the sentence the claim actually rests on, so a reader can judge it without leaving the page.
Sources without a url still work, and are the right shape for an internal document or a passage retrieved from your own store. Hide the printed list with showSourceList={false} when you are rendering it yourself somewhere else on the page.
API
sourcesCitationSource[]Id, title, and optional url and snippet. Order sets the numbering.childrenReactNodeThe text, with Citation markers placed inline.showSourceListbooleanRenders the numbered list below the text. Defaults to true.sourceListLabelReactNodeHeading for the source list.id (Citation)stringWhich source this marker points at.CitationSource
idstringWhat a citation mark refers to.titlestringShown in the list, and as the mark's accessible name.urlstringMakes the entry a link. Optional.snippetstringThe passage the claim rests on.Accessibility
Markers are real anchors to their list entry, so they work without hover or a pointer. Each one has an accessible name giving the number and the source title, and the visible digit is hidden from assistive technology to avoid reading it twice. A marker whose id is not in sources renders nothing rather than a dead link. External source links say that they open in a new tab.