Draw in the interface with a mod changedplugins/mods/interface
Nearest release: v2.1.301, published 2 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 15 Oct 2026 16:35 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 15 Oct 2026 16:37 UTC.
Upstream edited
Recorded here
Lines+32added
Lines−15removed
From line
216
where the diff opens
First seen
1 Oct 2026
this site's first read of the page
Recorded edits21to this page, all time
### Draw in the VS Code extension
The whole hunk
from line 216, old and new numbered
/
from line 216
216216| `Spinner`, `ToolProgress`, `TurnDuration` | Status lines for a turn: the line that animates while Claude works, a running tool's live progress line, and the line that closes a turn |
217217| `InfoNotice`, `SessionMode`, `PromptHint` | Status lines under the logo, the mode labels in the footer, and the hint line under the prompt |
218218
219`Pane`, `AskUserQuestion`, `Spinner`, `UserMessage`, `AssistantMessage`, and the tool call sites work in the terminal, the Desktop app, and the VS Code extension. The [render sites table](/docs/en/plugins/mods/reference#render-sites) lists where each of the others is raised.
220
219221At a site Claude Code already draws, your hook can change a detail, replace the drawing, or leave it alone. Select a tab to see each one applied to the spinner. The examples read a `calls` variable that another hook counts, as in the [tutorial mod](/docs/en/plugins/mods/create#write-a-mod-yourself).
220222
221223<Tabs>
from line 289
287289
288290The permission prompt isn't a render site, so a mod can't change what it shows. The question dialog, `AskUserQuestion`, is one, so a mod can change that. A tree for the dialog has to hold the reference exactly once, with your elements above it. Otherwise, Claude Code draws its own dialog.
289291
290The terminal and the Desktop app don't raise all the same sites. `Pane`, `AbovePrompt`, `Spinner`, and the transcript sites work in both. A few other status lines are raised in the terminal only. The [render sites table](/docs/en/plugins/mods/reference#render-sites) lists where each one is raised.
291
292292### Open a pane at the right time
293293
294294A pane appears only when your mod opens it. How and when you open it decides whether it takes keyboard focus, how much room it asks for, and whether it shows at all in a narrow terminal.
from line 316
316316| `rows` | The height to ask for when the pane sits above the prompt. The default is a third of the space. |
317317| `columns` | The width to ask for when the pane sits beside the transcript |
318318
319The [VS Code extension](#draw-in-the-vs-code-extension) ignores `rows` and `columns` and sizes the pane itself.
320
319321`focus`, `closeOnEscape`, and `holdToasts` are optional and accept only `true`. To leave one off, omit it. Passing `false` throws an error such as `ui.open: focus is true or left out`. To set one of them conditionally, add the field only when the condition holds. This call asks for keyboard focus only when `items` isn't empty:
320322
321323```javascript theme={null}
from line 342
340342
341343When the pane appears, `$.ui.open` resolves to `{ isPlaced: true }`. When the pane is waiting, `isPlaced` is `false` and `reason` is a string that says why. A waiting pane appears when the user opens it or widens the terminal. To say something is available without opening a pane, call `$.ui.toast('Your message')`, which shows a toast notification.
342344
345### Draw in the VS Code extension
346
347In the VS Code extension, what a mod draws appears in the chat panel with extension version 2.1.299 or later, in a session that [fetches feature flags](/docs/en/env-vars#features-that-need-feature-flag-fetching). In a `ui.render` hook for the panel, `e.surface` is `vscode`. To check which sites and elements the panel draws, and which props it ignores, see the [render sites](/docs/en/plugins/mods/reference#render-sites) and [elements](/docs/en/plugins/mods/reference#elements) tables.
348
349To make a mod work in the panel as well as the terminal, account for these differences:
350
351* **Panes**: in a panel 800 pixels or wider, panes dock beside the conversation, `e.props.placement` is `'dock'`, and `e.viewport.isFullscreen` is `true`. In a narrower panel, panes draw above the prompt box, `placement` is `'inline'`, and a pane shows 12 rows before it scrolls.
352* **Toasts and status lines**: these sit in one stack directly above the prompt box, together with the panes when the panel is narrow, with one status line per mod. The stack is hidden while a permission request or a dialog shows.
353* **Labels**: the panel frames what a mod draws or rewrites and labels it with the plugin's name, as in **From the token-chart plugin** or **Edited by the token-chart plugin**.
354* **Keyboard**: buttons have no hotkeys, so the user clicks a button or presses Enter on it once it has focus.
355* **Keyboard focus**: the panel grants a pane's `focus: true` request only when the panel has focus, Claude is idle with no question waiting, and the prompt box is empty.
356* **The question dialog**: at `AskUserQuestion`, the panel keeps its own question card and draws your elements above it. Props you pass to `next` don't change the card.
357* **Prompt suggestions**: `$.prompt.suggest` resolves with `isShown: false` and shows nothing, because the panel keeps the prompt box's dim suggestion text for Claude's own suggestions.
358* **Text limits**: the panel cuts a status line at 200 characters or two rows, and a toast at 500 characters.
359
343360## Build a tree from elements
344361
345What a `ui.render` hook returns is an element tree: a description of what to draw, made of boxes, text, and controls nested inside each other. You describe the drawing, and Claude Code renders it in the terminal or the Desktop app.
362What a `ui.render` hook returns is an element tree: a description of what to draw, made of boxes, text, and controls nested inside each other. You describe the drawing, and Claude Code renders it in whichever app the user has open: the terminal, the Desktop app, or the VS Code extension.
346363
347364To get the elements, call `$.ui.resolve(e)` in your hook, as in `const { Box, Text, Button } = $.ui.resolve(e)`. Each element is a function. You pass it props, and you put the elements and strings that go inside it in `children`.
348365
from line 439
422439| `Text` | Styled text. Takes `color`, `bold`, `dimColor`, `italic`, and `wrap`. A `color` is a theme key or a color such as `'red'`. A `wrap` is `'wrap'`, `'truncate'`, `'truncate-start'`, `'truncate-middle'`, or `'truncate-end'`. | Everywhere |
423440| `Button` | A control that calls `onPress` | Everywhere |
424441| `Link`, `Code`, `Markdown` | A link with `href` and an optional `label`, a code block, and text formatted the way Claude's replies are. `Markdown` takes its content in a `text` prop, not in `children`, and needs a `key` when you pass `onLinkPress`. | Everywhere |
425| `Input`, `Select` | A text field and a dropdown | Terminal, Desktop |
426| `Svg` | An SVG document | Desktop |
442| `Input`, `Select` | A text field and a dropdown | Terminal, Desktop, VS Code |
443| `Svg` | An SVG document | Desktop, VS Code |
427444| `Client` | A region drawn by a second file of yours, for animation and pointer input. That file gets no mods API. It reaches your hooks by posting data, which arrives as a `ui.message` event. If it fails to load, draw, or run, your hooks receive a [`ui.fault`](/docs/en/plugins/mods/reference#interface) event. | Terminal, Desktop |
428445| `Raster`, `Image` | A [grid of colored cells](#draw-a-grid-of-colored-cells), and a picture | Terminal |
429446
from line 451
434451In a session started with `--plugin-dir`, a transcript line says so, such as `ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own`. The [debug log](/docs/en/plugins/mods/troubleshoot#read-the-debug-log) records it as `ui.render (Pane): a hook returned a tree that does not validate` with the same reason. Nothing else appears in the session, so when a drawing doesn't show up, check that line or the log.
435452
436453<h3 id="link-in-the-desktop-app">
437 `Link` in the Desktop app
454 `Link` in the Desktop app and VS Code
438455</h3>
439456
440In the Desktop app, a `Link` draws as plain text unless its `href` meets these requirements:
457In the Desktop app and the VS Code extension, a `Link` draws as plain text unless its `href` meets these requirements:
441458
442459* **Scheme and host**: an `https:` URL, or an `http://localhost` URL such as `http://localhost:3000`
443460* **No `@`**: write an `@` in the path or query as `%40`
from line 474
457474
458475For a heat map, a sparkline, or a game board in the terminal, draw one `Raster` and not a `Box` for each cell. A `Raster` takes a `key`, its size in `columns` and `rows`, and `cells`, a base64 string that packs every cell. Each cell is three numbers: the character's code point, its color, and its background color. A color is a 24-bit RGB value in hexadecimal, such as `0xc62828` for a red. The value `0x01000000`, one above that range, means the terminal's default.
459476
460The Desktop app has no `Raster`, so check `e.surface` and draw text there. This pane body draws a three by two heat map:
477Apps other than the terminal have no `Raster`, so check `e.surface` and draw text there. This pane body draws a three by two heat map:
461478
462479```javascript theme={null}
463480// The value that means "use the terminal's default color"
from line 530
513530
514531Your mod never reads the keyboard itself. The user presses a key, Claude Code decides which of your controls it's for, and that control's callback runs. Apart from a [digit hotkey on the band](/docs/en/plugins/mods/reference#elements), that happens only while your pane or band has keyboard focus. The rest of the time, keys go to the prompt.
515532
533In the [VS Code extension](#draw-in-the-vs-code-extension), buttons have no hotkeys: the user clicks a button or presses Enter on it, and types into fields.
534
516535#### How a pane gets keyboard focus
517536
518537A pane gets keyboard focus when:
from line 561
542561
543562#### Set a hotkey and the first focus
544563
545These props on a control decide how the keyboard reaches it:
546
547564* **`hotkey`**: to let the user press a `Button` with one key, give it a `hotkey` of one digit or one lowercase letter, as in `hotkey: 'a'`
548* **`autoFocus`**: to choose which control has the focus when the pane opens, add `autoFocus: true` to it. The prop accepts only `true`, so omit it on the other controls.
565* **`autoFocus`**: to choose which control has the focus when the pane opens, add `autoFocus: true` to it. In the [VS Code extension](#draw-in-the-vs-code-extension), `autoFocus` does nothing: the control draws as if you had left it out, and you don't need to change anything. The prop accepts only `true`, so omit it on the other controls.
549566
550567How a hotkey shows depends on the button and the app:
551568
552| Button | In the terminal | In the Desktop app |
553| :- | :- | :- |
554| With brackets, the default | `[ Add one ]`, with no hotkey shown | The label with a small key beside it |
555| With `plain: true` | `1: One` | The label with a small key beside it |
569| Button | In the terminal | In the Desktop app | In the VS Code extension |
570| :- | :- | :- | :- |
571| With brackets, the default | `[ Add one ]`, with no hotkey shown | The label with a small key beside it | The label, and the hotkey does nothing |
572| With `plain: true` | `1: One` | The label with a small key beside it | The label, and the hotkey does nothing |
556573
557574In the terminal, name the key in a bracketed button's label, or use `plain: true`, so the user can see what to press. The [elements reference](/docs/en/plugins/mods/reference#elements) has the other `Button` rules: `action`, digit hotkeys on the band, and two buttons on one hotkey.
558575
No line in this hunk matches that.