Skip to main content

TerminalConsole

Surfacesterminal

The console FRAME: the one resting surface a coding-agent console draws — edge, radius, ground and elevation — so a transcript, banner, permission prompt, composer and status bar sit inside it as flush regions separated by a single seam instead of as separately framed cards.

import { TerminalConsole } from "@elabs-ai/components-terminal";
Install
npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-terminal

Skipping the @source line is the most common first failure: Tailwind does not scan node_modules, so every component renders unstyled. The one-time wiring

Prompt for your coding agent

Paste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.

Add the brand-ui TerminalConsole component (from @elabs-ai/components-terminal) to this project.

1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`.
2. Look TerminalConsole up before writing code: `npx -y @elabs-ai/components-cli docs TerminalConsole` prints its real props, variants, what it pairs with and what to avoid.
3. If @elabs-ai/components-terminal is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-terminal` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-terminal`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`.
4. Use it here: [the screen or file, and what it should show]
5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop.
6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.

Avoid

  • Stacking a second framed piece inside it — a region never draws its own radius, border, shadow or ground; ADR 0033's whole point is exactly one edge per console.
  • Adding a `variant`, transcript state, expansion state or a clock to its context — it publishes one static boolean, forever, the same prohibition `TerminalSurface`'s `variant` context carries.
  • Negating a region's frame with `rounded-none border-0 shadow-none` instead of omitting it — `TerminalSurface`'s frame-aware branch OMITS those classes; a caller who wants a framed region back adds them through `className`.
  • Nesting a console inside another console — a split view of two sessions is two consoles side by side in a layout, never a frame inside a frame.

Works with

Examples

2 live examples

Transcript Only

A framed transcript alone — the single-region case a console degrades to.

API

TerminalConsole

Also accepts: HTMLAttributes<HTMLDivElement>

TerminalSurface

PropTypeDescription
variantTerminalVariantGutter grammar for every row inside. A row may override it per-row.
gutterstringWidth of the gutter track, as any CSS length. Written to the local `--terminal-gutter` custom property — deliberately NOT a theme token: it does not vary by theme and no other package needs it. A caller's own `style` still wins over this.
loadingboolean`true` while the transcript has no renderable content yet (restoring a past session). Renders layout-shaped skeleton rows through the REAL `TerminalRow`, so the placeholder cannot drift from the grid it stands in for. Never a spinner.
loadingRowsnumberHow many skeleton rows to reserve while `loading`.
loadingLabelstringOverrides the announced loading label. Defaults to the shared "Loading…".

Also accepts: HTMLAttributes<HTMLDivElement>

TerminalBanner

PropTypeDescription
titlestringSession/agent name. Anchors the card — every other section is independently optional and renders nothing when omitted.
modelstringActive model, e.g. "gpt-5.1-codex".
workspacestringWorking directory / project path. Wraps instead of truncating (see above).
versionstringBuild/version string, e.g. "v2.4.0".
capabilitiesSessionCapability[]What this session can reach.
whatsNewSessionWhatsNewItem[]Recent changes, shown as a short list.
quickActionsSessionQuickAction[]Shortcut actions, rendered as real menu-row buttons with optional key hints.
logoReactNodeA caller-supplied mark, rendered beside `title`. This package ships no vendor logo of its own — bring your own `ReactNode`, or omit it.
variantTerminalVariantGutter grammar for the banner's internal rows. Omitted, they inherit the surrounding `TerminalSurface`; passed, it overrides it for this banner — the same override contract `TerminalRow` itself exposes.
levelHeadingLevelSemantic heading level for `title`. Default `2` (a banner typically sits above a transcript inside a page that already owns its own `<h1>`).

Also accepts: ComponentProps<"div">

TerminalComposer

PropTypeDescription
valuestringComposer text. Mirrors the native platform (`value`/`defaultValue`/ `onChange`) rather than a custom `onValueChange` — this IS a text input. Omit `value` for the uncontrolled default (`defaultValue ?? ""`).
defaultValuestring
onChangeChangeEventHandler<HTMLTextAreaElement>
onSubmit(value: string) => voidFires on Enter (no Shift, not composing) or the submit affordance, with the current text. Fires whenever there is non-whitespace text — INCLUDING while `busy` (ADR 0022 case 3, #128): a mid-turn follow-up can always be composed and submitted, exactly like `PromptInputTextarea`'s own contract. brand-ui asserts nothing about what a mid-turn submit MEANS (queued, interleaved, or something else) — that is the app's own `onSubmit`/runtime (D5). The caller decides whether/how to clear `value`; uncontrolled, the composer clears itself.
placeholderstring@default the localized "Type your next instruction…"
busybooleanTrue while the agent is actively working on a previous turn. The submit affordance becomes Stop ONLY when the well is also empty AND `onStop` is given — i.e. this composer is the one meant to own cancellation (ADR 0022's four-case contract, #128). A non-empty well always shows Send, so a mid-turn follow-up can still be typed and submitted; `busy` with no `onStop` never shows Stop at all (some other control, e.g. `TerminalWorking`, owns cancellation instead). Enter-to-submit is never disabled by `busy` alone.
onStop() => voidFires when the person activates the Stop affordance, or presses Escape, while `busy`. Passing this prop is also what tells the composer it OWNS cancellation — omit it when another control (e.g. `TerminalWorking`) is the dedicated Stop, so the two don't both render a control named "Stop" for the same action (#128).
modesOperatingMode[]App-defined operating modes (#117 — no vendor union here). Omitted, no mode indicator renders.
modestring
defaultModestring
onModeChange(id: string) => void
effortLevelsEffortLevel[]App-defined reasoning-effort scale, ordered low to high. Omitted, no effort indicator renders.
effortstring
defaultEffortstring
onEffortChange(id: string) => void
effortLabelstringAccessible name for the effort scale. @default the localized "Effort"
shortcutsTerminalComposerShortcut[]The hints shown at rest, e.g. `[{ keys: "Enter", label: "send" }]`. @default a localized Enter-to-send / Shift+Enter-for-newline pair. The cancel hint is added automatically while this composer can actually cancel the turn (`busy` AND `onStop` — see `busy`) — do not include it here; override it with `cancelShortcut` instead.
cancelShortcutTerminalComposerShortcutOverrides the cancel hint shown while this composer owns cancellation (`busy` AND `onStop`, see `busy`). @default the localized "Esc" / "cancel" pair.
variantTerminalVariantGutter grammar forwarded to the internal `TerminalSurface`.
textareaRefRef<HTMLTextAreaElement>Ref to the underlying `<textarea>` DOM node. The `ref` this component already forwards reaches the root `TerminalSurface` div, not the field itself — a caller that needs caret math on every keystroke (a slash-command palette, an `@`-mention overlay) reaches for this instead. Additive and optional: every existing caller is unaffected. See `TerminalSlashMenu` (#117 T12) for the reference consumer.

Also accepts: Omit< HTMLAttributes<HTMLDivElement>, "onChange" | "onSubmit" >

TerminalPermission

PropTypeDescription
titleReactNode@default the localized "Bash command"
previewReactNodeWhat will actually run — a command string today, a diff hunk tomorrow. Arbitrary content: never typed as a string. Omitted, no preview row renders.
questionReactNode@default the localized "Do you want to proceed?"
optionsApprovalOption[]The scoped choices. Defaults to the three verbatim upstream options — "Yes" (`once`), "Yes, and don't ask again this session" (`session`), and the vendor-free decline-and-redirect option (`deny`).
valuestring
defaultValuestring
onValueChange(id: string) => void
onConfirm(option: ApprovalOption, reason?: string) => voidFires when an option is chosen — by click or by ANY keyboard move within the radio group (arrow keys/Home/End select immediately, per the native/ARIA radiogroup pattern Radix implements, matching `ApprovalCardOptions` — there is no separate highlight-vs-commit step) — and again on every edit of the reason field while a `deny`-scoped option is selected, so the caller always has the latest pair.
reasonstringControlled text for the deny scope's optional "what to do instead" explanation.
defaultReasonstring
onReasonChange(reason: string) => void
reasonPlaceholderstring
variantTerminalVariantGutter grammar for every row this prompt renders. Omitted, it inherits the surrounding `TerminalSurface`; passed, it overrides it — the same override contract `TerminalRow` itself exposes.

Also accepts: Omit< HTMLAttributes<HTMLDivElement>, "children" | "title" >

TerminalStatusBar

PropTypeDescription
branchstringCurrent git branch.
workspacestringCurrent working directory. A long path truncates in place; the full value is still reachable via its native `title`.
connectionsTerminalStatusBarConnectionsIntegration/MCP connection progress.
contextTerminalStatusBarContextContext-window usage, already formatted by the caller.
turnTerminalStatusBarTurnProgress through the current turn's steps.

Also accepts: HTMLAttributes<HTMLDivElement>

Theming

The tokens each state resolves to. Change the token and every theme follows.

StateToken
framerounded-lg border-terminal-border bg-terminal-background shadow-sm
seamborder-t border-terminal-border