Elements

Link preview

A returned URL unfurled into a card with enough context to decide whether to open it.

fig. 01 · plays once, replay from the corner

Installation

npx shadcn@latest add "@assistant-ui/elements-link-preview"
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.

Link preview turns a URL a tool returned into a small, readable card. It keeps the destination, title, description, and image together without making the whole transcript feel like a browser.

Getting started

A backend tool can return the URL metadata it resolved. Its renderer waits for that result, then hands the fields directly to LinkPreview.

Render a tool result

components/assistant-ui/elements/preview-link-tool-ui.tsx
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { LinkPreview } from "@/components/assistant-ui/elements/link-preview";

type PreviewLinkResult = {
  href: string;
  title?: string;
  description?: string;
  image?: string;
  siteName?: string;
};

export const PreviewLinkToolUI: ToolCallMessagePartComponent<
  Record<string, never>,
  PreviewLinkResult
> = ({ result }) => (result ? <LinkPreview {...result} /> : null);

Register the backend tool

app/toolkit.ts
import { defineToolkit } from "@assistant-ui/react";
import { PreviewLinkToolUI } from "@/components/assistant-ui/elements/preview-link-tool-ui";

export const toolkit = defineToolkit({
  preview_link: {
    type: "backend",
    render: PreviewLinkToolUI,
  },
});

The backend returns { href, title, description, image, siteName } as its tool result. While no result exists, the renderer returns null.

Anatomy

<article data-slot="link-preview" data-layout="card | compact">
  <div data-slot="link-preview-image" />
  <div>
    <span>{/* favicon or site initial, then site name */}</span>
    <a>{/* title and an accessible new-tab label */}</a>
    <span>{/* description */}</span>
  </div>
</article>

The default card layout puts the image above the text. compact moves a 64px thumbnail to the leading edge. If the image cannot load, its frame disappears. A supplied favicon takes the site square's place, otherwise the card uses the first letter of the site name or host.

When href is safe, the title opens it in a new tab and its hit target covers the card. Unsafe URLs render the same information without a link. Missing title and siteName fall back to the URL host when one is available.

Examples

Compact layout

Use the compact shape when the card shares a dense answer with other links.

<LinkPreview
  href="https://react.dev/learn/you-might-not-need-an-effect"
  title="You might not need an Effect"
  siteName="React"
  layout="compact"
/>

Unsafe URL behavior

The component never turns an unsafe protocol into a link. It still shows caller-provided text, so a transcript can preserve the tool result without exposing navigation.

<LinkPreview
  href="javascript:alert(1)"
  title="Untrusted result"
  description="Shown as plain metadata, with no link."
/>

API reference

Tool-call render props

PropTypeDescription
result{ href, title?, description?, image?, siteName? } | undefinedThe backend's resolved metadata. Render nothing until it exists.
statusToolCallMessagePartStatusThe tool-call lifecycle state when the renderer needs it.
toolCallIdstringStable id for this tool invocation.

Register the renderer on a backend tool's render field. See Tool UI for the complete render-prop surface.