Elements

Terminal block

Command output that streams line by line and ends with an exit status.

pnpm vitest run composer
RUN v4.0.5 /apps/docs
fig. 01 · plays once, replay from the corner

Installation

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

A terminal block echoes a command and reveals its output one line at a time, ending in a checkmark once it settles or a blinking cursor while it runs. With a runtime the command and its lines come from a tool call's args and result; standalone you supply both directly.

Getting started

Render the tool call

components/assistant-ui/elements/run-command-tool-ui.tsx
"use client";

import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { TerminalBlock } from "@/components/assistant-ui/elements/terminal-block";

type RunCommandArgs = { command: string };
type RunCommandResult = { output: string };

export const RunCommandToolUI: ToolCallMessagePartComponent<
  RunCommandArgs,
  RunCommandResult
> = ({ args, result, status }) => {
  const lines = result ? result.output.split("\n") : [];
  return (
    <TerminalBlock
      command={args.command ?? ""}
      lines={lines}
      visibleCount={lines.length}
      done={status.type !== "running"}
    />
  );
};

Register the tool

app/toolkit.ts
import { defineToolkit } from "@assistant-ui/react";
import { RunCommandToolUI } from "@/components/assistant-ui/elements/run-command-tool-ui";

export const toolkit = defineToolkit({
  run_command: {
    type: "backend",
    render: RunCommandToolUI,
  },
});
app/MyRuntimeProvider.tsx
import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
import { toolkit } from "./toolkit";

export function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
  const runtime = useChatRuntime();
  const config = AuiConfig({ tools: Tools({ toolkit }) });
  return (
    <AssistantRuntimeProvider runtime={runtime} config={config}>
      {children}
    </AssistantRuntimeProvider>
  );
}

The backend that owns run_command supplies its schema and executor; see Backend tools.

Anatomy

<div data-slot="terminal-block">
  <div>{/* command text, a spinner while running, a checkmark + "exit 0" once done */}</div>
  <div>{/* lines revealed up to visibleCount; the last line stays bright; a blinking cursor while !done */}</div>
</div>

The exit line always reads exit 0 regardless of the real outcome: done only switches the header between a spinner and a checkmark, there is no prop for a nonzero exit code or a failed run. The body reserves a minimum height so the block does not resize as lines arrive. visibleCount clamps to the length of lines, and only the most recently revealed line renders at full opacity; earlier lines settle to a dimmer tone.

Examples

Ink variant

variant="ink" inverts the block to a solid dark panel and lightens the text against it. Every other prop and all behavior stay identical.

<TerminalBlock variant="ink" command="pnpm vitest run" lines={LINES} visibleCount={2} done />

Restyle the terminal

Both lanes take className on the root. The paper variant's background and border come from the shared paper token in surfaces.tsx; the ink variant sets its own background directly.

<TerminalBlock className="max-w-none" /* ... */ />

API reference

Tool-call render props

PropTypeDescription
args{ command: string }The command the model asked to run.
result{ output: string } | undefinedThe captured output, once the call completes.
statusToolCallMessagePartStatusstatus.type === "running" while the process is still executing.

See Tool UI for the full render-prop surface and for backend tool registration.