import { TerminalConsole } from "@elabs-ai/components-terminal";npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-terminalPrompt 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
Transcript Only
A framed transcript alone — the single-region case a console degrades to.
API
TerminalConsole
TerminalSurface
| Prop | Type | Description |
|---|---|---|
variant | TerminalVariant | Gutter grammar for every row inside. A row may override it per-row. |
gutter | string | Width 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. |
loading | boolean | `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. |
loadingRows | number | How many skeleton rows to reserve while `loading`. |
loadingLabel | string | Overrides the announced loading label. Defaults to the shared "Loading…". |
TerminalBanner
| Prop | Type | Description |
|---|---|---|
title | string | Session/agent name. Anchors the card — every other section is independently optional and renders nothing when omitted. |
model | string | Active model, e.g. "gpt-5.1-codex". |
workspace | string | Working directory / project path. Wraps instead of truncating (see above). |
version | string | Build/version string, e.g. "v2.4.0". |
capabilities | SessionCapability[] | What this session can reach. |
whatsNew | SessionWhatsNewItem[] | Recent changes, shown as a short list. |
quickActions | SessionQuickAction[] | Shortcut actions, rendered as real menu-row buttons with optional key hints. |
logo | ReactNode | A caller-supplied mark, rendered beside `title`. This package ships no vendor logo of its own — bring your own `ReactNode`, or omit it. |
variant | TerminalVariant | Gutter 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. |
level | HeadingLevel | Semantic heading level for `title`. Default `2` (a banner typically sits above a transcript inside a page that already owns its own `<h1>`). |
TerminalComposer
| Prop | Type | Description |
|---|---|---|
value | string | Composer 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 ?? ""`). |
defaultValue | string | |
onChange | ChangeEventHandler<HTMLTextAreaElement> | |
onSubmit | (value: string) => void | Fires 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. |
placeholder | string | @default the localized "Type your next instruction…" |
busy | boolean | True 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 | () => void | Fires 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). |
modes | OperatingMode[] | App-defined operating modes (#117 — no vendor union here). Omitted, no mode indicator renders. |
mode | string | |
defaultMode | string | |
onModeChange | (id: string) => void | |
effortLevels | EffortLevel[] | App-defined reasoning-effort scale, ordered low to high. Omitted, no effort indicator renders. |
effort | string | |
defaultEffort | string | |
onEffortChange | (id: string) => void | |
effortLabel | string | Accessible name for the effort scale. @default the localized "Effort" |
shortcuts | TerminalComposerShortcut[] | 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. |
cancelShortcut | TerminalComposerShortcut | Overrides the cancel hint shown while this composer owns cancellation (`busy` AND `onStop`, see `busy`). @default the localized "Esc" / "cancel" pair. |
variant | TerminalVariant | Gutter grammar forwarded to the internal `TerminalSurface`. |
textareaRef | Ref<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. |
TerminalPermission
| Prop | Type | Description |
|---|---|---|
title | ReactNode | @default the localized "Bash command" |
preview | ReactNode | What will actually run — a command string today, a diff hunk tomorrow. Arbitrary content: never typed as a string. Omitted, no preview row renders. |
question | ReactNode | @default the localized "Do you want to proceed?" |
options | ApprovalOption[] | 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`). |
value | string | |
defaultValue | string | |
onValueChange | (id: string) => void | |
onConfirm | (option: ApprovalOption, reason?: string) => void | Fires 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. |
reason | string | Controlled text for the deny scope's optional "what to do instead" explanation. |
defaultReason | string | |
onReasonChange | (reason: string) => void | |
reasonPlaceholder | string | |
variant | TerminalVariant | Gutter grammar for every row this prompt renders. Omitted, it inherits the surrounding `TerminalSurface`; passed, it overrides it — the same override contract `TerminalRow` itself exposes. |
TerminalStatusBar
| Prop | Type | Description |
|---|---|---|
branch | string | Current git branch. |
workspace | string | Current working directory. A long path truncates in place; the full value is still reachable via its native `title`. |
connections | TerminalStatusBarConnections | Integration/MCP connection progress. |
context | TerminalStatusBarContext | Context-window usage, already formatted by the caller. |
turn | TerminalStatusBarTurn | Progress through the current turn's steps. |
Theming
The tokens each state resolves to. Change the token and every theme follows.
| State | Token |
|---|---|
| frame | rounded-lg border-terminal-border bg-terminal-background shadow-sm |
| seam | border-t border-terminal-border |