Elements

Sources

Runtime sources with favicon links for URLs and file badges for documents.

fig. 01

Installation

Install manually
npx shadcn@latest add "@assistant-ui/sources"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

Sources has two lanes. The runtime binding renders each message source as a compact chip. The static element collects citations behind one Sources pill and reveals linked cards with snippets and publication details.

Getting started

Register the message source renderer

Register Sources as the Source renderer on MessagePrimitive.Parts. Every source part in the message routes through it automatically.

components/assistant-ui/elements/thread.aui.tsx
import { Sources } from "@/components/assistant-ui/elements/sources.aui";
import { MessagePrimitive } from "@assistant-ui/react";

function AssistantMessage() {
  return (
    <MessagePrimitive.Root>
      <MessagePrimitive.Parts components={{ Source: Sources }} />
    </MessagePrimitive.Root>
  );
}

A safe url part becomes a link, a document part becomes a badge, and any other shape renders nothing.

Anatomy

Runtime source chip

{/* sourceType: "url" */}
<a data-slot="source" href={safeHref(url)}>
  <img data-slot="source-icon" />
  <span data-slot="source-title">{title ?? domain}</span>
</a>

{/* sourceType: "document" */}
<span data-slot="source">
  <span data-slot="source-document-icon" />
  <span data-slot="source-title">{title}</span>
</span>

The favicon requests https://icons.duckduckgo.com/ip3/<domain>.ico by default. When that image fails to load, the icon falls back to the domain initial. The visible title is title when present, otherwise the host with www. removed. A document source never links. An unsafe URL renders as plain text, never as an href.

Examples

Custom favicon

Compose the exported sub-parts when the default favicon service is not appropriate. Keep the safe URL check before providing an href:

import { safeHref } from "@/components/assistant-ui/utils/href";
import { Source, SourceIcon, SourceTitle } from "@/components/assistant-ui/elements/sources.aui";

function CustomSource({ url, title }: { url: string; title: string }) {
  const href = safeHref(url);
  if (!href) return <span>{title}</span>;

  return (
    <Source href={href} variant="secondary">
      <SourceIcon
        url={url}
        faviconUrl={(domain) => `https://logo.clearbit.com/${domain}`}
      />
      <SourceTitle>{title}</SourceTitle>
    </Source>
  );
}

API reference

Runtime bindings

ExportRendersNotes
Sourcesa, text chip, or badgeThe Source message-part renderer. Pass to MessagePrimitive.Parts as components.Source.
Sources.RootaThe link for a url source. Accepts variant, size, href, and other anchor props.
Sources.Iconimg or fallback spanFavicon for a url source. Accepts url and an optional faviconUrl resolver.
Sources.TitlespanTruncated title or domain text, shared by both source shapes.

Source part

FieldTypeDescription
sourceType"url" | "document"Which shape to render.
idstringSource identifier.
urlstringLink target for "url". Unsafe values render as text.
titlestringOptional for "url" (falls back to the domain); required for "document".
mediaTypestringRequired when sourceType is "document". Not read by Sources.
filenamestringOptional, "document" only. Not read by Sources.