Components
Sources
Citations as numbered chips or cards, linked from [n] markers in the answer.
Example
Sources · 3
Interactive, and the same on the components page. Try the keyboard below on it.
Installation
npm i signoff-ui aiimport { Sources, getSourceParts } from 'signoff-ui';
// Once per app (with Tailwind v4: @import 'signoff-ui/tailwind.css'; in your CSS)
import 'signoff-ui/styles.css';npx shadcn@latest add https://signoff-ui.vercel.app/r/sources.jsonimport { Sources } from '@/components/signoff-ui/sources';
import { getSourceParts } from '@/components/signoff-ui/lib/ai';The styles are once per app; Getting started has the Tailwind v4 and plain CSS options.
Usage
// Send them from the server with toUIMessageStreamResponse({ sendSources: true }).
<Sources sources={getSourceParts(message.parts)} variant="cards" />How it works
source-url and source-document parts as numbered chips or cards. In AgentMessage, [n] markers in the text become links to the matching source. It has no state, so it renders on the server too.
API reference
Generated from the types the package ships, so it matches the version you install.
Sources
Citation list as compact chips or richer cards. External links open in a new tab.
Props
sourcesrequiredReadonlyArray<SourcePart | SourceItem>variant'chips' | 'cards'Default'chips'labelstring | nullVisible heading. Default "Sources" (
labels.sources.label); passnullto hide it (it stays as the accessible name).idPrefixstringDefault'source'Prefix for element ids (
${idPrefix}-1, …) so inline citations can link to a source.labelsSignoffLabelsInputWords to use instead of the English defaults: see
SignoffLabelsProvider.
Other props go to the root <div>: className (merged with tailwind-merge), id, aria-*, data-* and event handlers.
SourceItem
A source in app-level shape. AI SDK source-url / source-document parts are accepted too.
idrequiredstringurlstringtitlestringdescriptionstringOptional snippet shown in the cards variant.
filenamestringFor documents.
mediaTypestring
Accessibility
- An ordered list named by
label("Sources"), not a landmark, so a long conversation doesn’t flood landmark navigation. - Links open in a new tab and say so to screen readers: "(opens in a new tab)".
- Each item has the id
${idPrefix}-${n}, which[n]citation links inMarkdownandAgentMessagepoint to. - It has no state and no
'use client', so it renders in Server Components too.
Theming
Every color, radius and font is a CSS variable, so the overrides in Theming apply, on :root or scoped to any element. These hooks are read from the component's markup.
- Slots (data-slot)
- signoff-sources
- State attributes
- data-varianton
signoff-sources'chips' | 'cards' - Tokens it uses
- --signoff-accent-fg--signoff-border--signoff-border-strong--signoff-fg--signoff-fg-muted--signoff-fg-subtle--signoff-font-mono--signoff-font-sans--signoff-ring--signoff-surface--signoff-surface-2
/* Only this component, and only inside .settings-panel */
.settings-panel [data-slot='signoff-sources'] {
--signoff-radius: 4px;
}
[data-slot='signoff-sources'][data-variant='chips'] {
outline: 1px solid var(--signoff-accent);
}