Mischief

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

  1. 1PostgreSQL — Unique indexes(opens in a new tab)Null values are not considered equal by a unique constraint.
  2. 20004_add_email_index.sqlCreates the index before the backfill statement runs.

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/inline-citations
import { InlineCitations } from "mischief-ui/inline-citations"
Also installs
  • lucide-react

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/inline-citations/inline-citations.tsx
"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>
Both rfc marks read as the same number; notes takes the next one.

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.