Elements

Elements · AUI connected · AUI

Image

Image message parts with preview, loading states, actions, and a fullscreen view.

Outline (click to zoom)
landscape-sm.jpg
landscape-default.jpg
Muted
landscape-sm.jpg
landscape-default.jpg
fig. 01

Installation

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

Image renders one image message part: a preview that click-zooms into a fullscreen view, a loading placeholder while the pixels arrive, and an error state when generation is blocked. With a runtime it renders straight from a message's image part; standalone you hand it the part yourself.

Getting started

Render images from a message

Register Image as the Image renderer on MessagePrimitive.Parts.

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

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

The Thread element already renders both assistant and user image parts through Image, so installing @assistant-ui/thread gives you this without any of the above.

Anatomy

<div data-slot="image-root" data-variant={variant} data-size={size}>
  {/* status.type === "running": */}
  <div data-slot="image-generating" />

  {/* status.type === "incomplete" && status.reason === "content-filter": */}
  <div data-slot="image-content-filter-error" />

  {/* otherwise: */}
  <div className="aui-image-zoom-trigger" role="button" aria-label="Click to zoom image">
    <div data-slot="image-preview">
      <div data-slot="image-preview-loading" /> {/* until loaded or errored */}
      <img /> {/* or, on error: */}
      <div data-slot="image-preview-error" />
    </div>
  </div>
  <span data-slot="image-filename" /> {/* omitted when there is no filename */}
</div>

The three top-level states are exclusive and resolve from status: running shows only the generating placeholder, incomplete with reason content-filter shows only the blocked-image message, and everything else (including no status) shows the real preview. Clicking the preview, or pressing Enter while it has focus, opens the image in a fullscreen dialog: a portal overlay with a focus trap, Escape-to-close, click-outside-to-close, and the page scroll locked while it is open. ImageActions (download, copy, regenerate) is not part of the default render tree; add it yourself, as shown below.

Examples

Add download, copy, and regenerate actions

ImageActions renders a download button and a copy-to-clipboard button; passing onRegenerate adds a third button that shows a spinner while it runs. Download picks a file extension from the image's MIME type (defaulting to .png) and uses part.filename when set; copy requires a browser with the Clipboard API and silently no-ops if it is unavailable or the write fails.

import { Image } from "@/components/assistant-ui/elements/image";

function ImageWithActions(part: React.ComponentProps<typeof Image>) {
  return (
    <Image.Root>
      <Image.Zoom src={part.image}>
        <Image.Preview src={part.image} />
      </Image.Zoom>
      <Image.Actions part={part} onRegenerate={() => refetchImage(part)} />
    </Image.Root>
  );
}

Variants and sizes

<Image variant="ghost" size="lg" {...part} />
VariantDescription
outlineBorder around the frame (default)
ghostNo border
mutedMuted background while loading
SizeMax width
sm16rem
default24rem (default)
lg32rem
full100% of the container

API reference

Image

ExportRendersNotes
ImagedivThe Image message-part renderer. Pass to MessagePrimitive.Parts as components.Image.
Image.RootdivFrame with variant and size.
Image.Previewdiv + imgLoads src, shows a placeholder until it does, an error icon if it fails.
Image.Zoomtrigger + portal dialogWraps children in a click-to-zoom trigger for src.
Image.FilenamespanRenders nothing when there is no filename.
Image.GeneratingdivShown while status.type === "running".
Image.ContentFilterErrordivShown when status is incomplete with reason content-filter.
Image.ActionsdivDownload, copy, and (if onRegenerate is passed) regenerate buttons. Not rendered by default.

Image part

FieldTypeDescription
imagestringA URL or data URI for the image.
filenamestringOptional; shown under the preview and used as the download name.
statusMessagePartStatusDrives the generating / blocked / normal states.