Elements

50 / 122 · Structured output

Diagram

A drawn answer with zoom, reset, and a full-bleed view; you hand it the rendered graphic.

message flow100%
composerruntimeadapterprovider

Installation

npx shadcn@latest add "@assistant-ui/elements-diagram"

Usage

import { Diagram } from "@/components/elements/diagram";

<Diagram title="message flow" zoom={zoom} onZoomIn={zoomIn} onReset={reset}>
  <MermaidSvg chart={chart} />
</Diagram>

Props

title*stringWhat the diagram shows.
zoom*numberScale factor applied to the content. 1 is fit; the header prints it as a percentage.
children*React.ReactNodeThe rendered graphic. The element owns the frame and never parses or injects markup itself, so whatever renders your chart stays your choice.
onZoomIn() => voidCalled from the zoom-in control. Clamp the range yourself.
onZoomOut() => voidCalled from the zoom-out control.
onReset() => voidCalled to return to the default view.
onExpand() => voidCalled to open the diagram full screen.
classNamestringExtra classes merged onto the root.

Source

diagram.tsx
"use client";

import { MaximizeIcon, MinusIcon, PlusIcon, RotateCcwIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { ghostButton, mono, paper } from "./surfaces";

export function Diagram({
  title,
  zoom,
  children,
  onZoomIn,
  onZoomOut,
  onReset,
  onExpand,
  className,
}: {
  title: string;
  zoom: number;
  children: React.ReactNode;
  onZoomIn?: () => void;
  onZoomOut?: () => void;
  onReset?: () => void;
  onExpand?: () => void;
  className?: string;
}) {
  return (
    <div
      className={cn(
        paper,
        "flex w-full max-w-md flex-col overflow-hidden rounded-2xl",
        className,
      )}
    >
      <div className="flex items-center gap-1 px-3 py-2">
        <span className="min-w-0 flex-1 truncate text-[13px] font-medium">
          {title}
        </span>
        <span className={cn(mono, "text-foreground/30 shrink-0 tabular-nums")}>
          {Math.round(zoom * 100)}%
        </span>
        <button
          type="button"
          aria-label="Zoom out"
          onClick={onZoomOut}
          className={cn(ghostButton, "size-7 shrink-0")}
        >
          <MinusIcon className="size-3.5" />
        </button>
        <button
          type="button"
          aria-label="Zoom in"
          onClick={onZoomIn}
          className={cn(ghostButton, "size-7 shrink-0")}
        >
          <PlusIcon className="size-3.5" />
        </button>
        <button
          type="button"
          aria-label="Reset the view"
          onClick={onReset}
          className={cn(ghostButton, "size-7 shrink-0")}
        >
          <RotateCcwIcon className="size-3.5" />
        </button>
        <button
          type="button"
          aria-label="Open full screen"
          onClick={onExpand}
          disabled={!onExpand}
          className={cn(
            ghostButton,
            "size-7 shrink-0 disabled:pointer-events-none disabled:opacity-30",
          )}
        >
          <MaximizeIcon className="size-3.5" />
        </button>
      </div>

      <div className="border-foreground/[0.07] flex min-h-[10rem] items-center justify-center overflow-hidden border-t p-4">
        <div
          className="origin-center transition-transform duration-200 ease-out motion-reduce:transition-none"
          style={{ transform: `scale(${zoom})` }}
        >
          {children}
        </div>
      </div>
    </div>
  );
}