Home

Terminal Kit

Terminal Kit is a React component library for agent interfaces. Compose a session from messages, streamed responses, thinking states, file edits, questions, and an input. Each piece can also be used on its own.

A session in Terminal Kit

I often found myself recreating terminals on the web to showcase agent features. They never looked quite right or felt real.

I built Terminal Kit to give those demos a better starting point. I wanted to work through the details—the way a response streams in, the thinking states, the questions, and the input—and make them reusable across projects.

Getting started

In a React 19+ project with shadcn/ui initialized, install the full bundle to get all eight components and their shared theme styles.

npx shadcn@latest add "https://www.terminal-kit.com/r/full-bundle.json"

Eight primitives, one session

TerminalWindow holds the conversation. SessionContent arranges its entries. Message, StreamText, ThinkingIndicator, and EditBlock give each entry a form. Input and QuestionPrompt let the person respond.

The composition below brings all eight together. The question sits in the transcript and the input stays in the footer, so you can see both. Try an answer or submit a follow-up.

Eight primitives in one session

Start with a window, transcript, and input. This complete example keeps the prompt in local state; the shorter examples below show components you can add inside that window.

"use client";

import { useState } from "react";
import {
  TerminalWindow, SessionContent, Message, Input,
  StreamText,
} from "@/components/terminal-kit";

export function AgentSession() {
  const [prompt, setPrompt] = useState("Review this button.");
  const [value, setValue] = useState("");

  return (
    <TerminalWindow
      path="project / button.tsx"
      footer={<Input value={value} onValueChange={setValue}
        onSubmit={(text) => { setPrompt(text); setValue(""); }} />}
    >
      <SessionContent>
        <Message>{prompt}</Message>
        <StreamText mode="fade" speed={60}>
          I’ll check its hover and keyboard focus states.
        </StreamText>
      </SessionContent>
    </TerminalWindow>
  );
}

Word streaming

StreamText reveals a response one word at a time. Plain mode shows each word immediately; fade mode lets it arrive gradually. The same sentence below makes the difference visible.

I separated the delay between words from the duration of each fade. That lets a response arrive quickly while keeping its appearance soft. A single speed prop controls both when that level of adjustment is enough.

Text streaming
import { StreamText } from "@/components/terminal-kit";

<StreamText mode="fade" segmentDelay={60} fadeDuration={300}>
  I’ll check its hover and keyboard focus states.
</StreamText>

Thinking states

The default thinking indicator is a 3 × 3 dot matrix. A point travels around the perimeter, leaving a fading trail, while the center pulses independently. Speed, trail length, dot size, and the center pulse change its character.

Custom frames can change the motion entirely. The ripple travels from the center through the edges to the corners and back. The Claude-style spinner uses six typographic symbols. Here they are at the size they appear in a terminal.

Thinking indicators
import { ThinkingIndicator } from "@/components/terminal-kit";

<ThinkingIndicator variant="dots"
  dotProps={{ speed: 70, trail: 6, pulseCenter: false }}>
  Checking the interaction states
</ThinkingIndicator>

Input

The input looks like a terminal prompt and behaves like a text area. Type a message, move back through it, or use Shift + Enter for a new line. Enter submits a nonempty prompt and keeps focus in the input.

The block cursor follows the insertion point. Keyboard hints and status sit outside the editable text, so they never become part of what someone submits. This example echoes your prompts locally.

The prompt input
import { Input } from "@/components/terminal-kit";

<Input
  placeholder="Ask a follow-up…"
  onSubmit={(text) => console.log(text)}
  metaLeft="shift + enter for a new line"
/>

Questions

An agent sometimes needs a decision before it can continue. QuestionPrompt supports numbered choices, arrow-key navigation, Enter to select, and a custom answer.

In this example, the question occupies the footer until you answer. It then becomes a compact question-and-answer entry in the transcript, and the input returns. The decision remains part of the conversation without keeping the selection interface open.

Questions and answers
import { QuestionPrompt } from "@/components/terminal-kit";

<QuestionPrompt
  question="What should I verify next?"
  options={[
    { id: "keyboard", label: "Keyboard navigation" },
    { id: "motion", label: "Reduced motion" },
  ]}
  onSelect={(id, customAnswer) => console.log(id, customAnswer)}
/>

File edits

A file edit needs more context than a line of output. EditBlock shows the path, change counts, and numbered lines, with separate treatments for additions and removals.

Long edits collapse around the changed lines. Expand one to see the surrounding code, or switch between panel, compact, and flat layouts to see how the same edit fits different terminal arrangements.

File edits
import { EditBlock } from "@/components/terminal-kit";

<EditBlock
  file="components/button.tsx"
  code={'<button className="focus-visible:ring-2">Continue</button>'}
  startLine={12}
  highlightLines={[12]}
  layout="panel"
/>

Themes

Default, Grok, and Claude each have light and dark palettes. Use the theme controls at the bottom of the page to change the examples across this page.

The differences go beyond color. Default has a composer with a metadata tray. Grok keeps the input compact and inline. Claude stacks the input and footer. Thinking indicators and edit layouts adapt too, while individual components can override their theme’s defaults.

Themes and appearance
import { TerminalWindow } from "@/components/terminal-kit";

<TerminalWindow theme="claude" variant="light" path="project">
  {/* The session inherits this terminal’s theme. */}
</TerminalWindow>

Putting a session in motion

A scripted session uses the same components as an agent interface. SessionContent can reveal its children in order: a message, a response, an edit, then a follow-up. Replay this example to watch them arrive.

The pauses depend on the entry. A submitted message gets a short pause; an edit gets longer. StreamText supplies an estimated duration so the next entry doesn’t arrive halfway through a sentence.

Session composition
import {
  SessionContent, Message, StreamText, ThinkingIndicator,
} from "@/components/terminal-kit";

<SessionContent streaming autoScroll>
  <Message>Review this button.</Message>
  <StreamText speed={60} sessionPause={1200}>
    I’ll check its keyboard focus state first.
  </StreamText>
  <ThinkingIndicator sessionTail>Reviewing…</ThinkingIndicator>
</SessionContent>

How it works

The window holds the conversation; SessionContent coordinates what appears inside it. It uses each entry’s timing to pace the sequence, clears pending timers on replay, and skips the stagger for reduced motion. Input and question callbacks leave the application in control, so the same components can power a scripted demo or connect to a real agent.

Install it. Make it yours.

I chose a shadcn registry so installation copies the source into your project. Install one primitive or the full bundle, then change the code you own.

A team might want a different thinking state, input arrangement, or timing rule. The registry supplies the components, shared theme tokens, and dependencies; those decisions can then live in the team’s codebase.

npx shadcn@latest add "https://www.terminal-kit.com/r/stream-text.json"

The usage examples above import from the full bundle. When installing a single component, use its generated file path. See the installation documentation for setup and font configuration.

Documentation for humans and agents

The documentation pairs live examples with props, installation commands, and theming details. It also covers composition: where to put an active question, how to retain its answer, and how to coordinate streaming with thinking states.

I included a plain-text documentation index and a complete documentation file alongside the registry manifests. People can try a behavior in the browser; agents can read the same component APIs and assembly instructions directly.