Follow Discord
Sweep 03 Nov 2026 · 22:27Z Build v2.1.318 536 read Stable v2.1.311 Latest v2.1.318 Next v2.1.318 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

Draw in the interface with a mod changedplugins/mods/interface

Nearest release: v2.1.312, published an hour 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 27 Oct 2026 20:08 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 30 Oct 2026 18:07 UTC.

Upstream edited
Recorded here
Lines+132added
Lines−132removed
From line 177 where the diff opens
First seen 1 Oct 2026 this site's first read of the page
Recorded edits29to this page, all time

The whole hunk

from line 177, old and new numbered
/
lines
from line 177
177177 
178178The pane and the band are empty until a mod fills them. Select a tab to see what each one is and how to draw in it:
179179 
180**Pane**
180> [tab] Pane
181>
182> A pane is a sidebar beside the transcript in a wide fullscreen terminal, or a framed region above the prompt otherwise. With several panes open, each gets a tab that shows its title.
183>
184> A pane appears when your mod calls `$.ui.open` with an `id` you choose, as in `$.ui.open({ id: 'hello-tabs' })`. [Open a pane at the right time](#open-a-pane-at-the-right-time) covers the other fields and when a pane waits for a wider terminal.
185>
186> To draw in your pane, filter on `{ component: 'Pane' }` and check that `e.requestId` is your `id`.
181187 
182A pane is a sidebar beside the transcript in a wide fullscreen terminal, or a framed region above the prompt otherwise. With several panes open, each gets a tab that shows its title.
188> [tab] Band above the prompt
189>
190> The band is a strip directly above the prompt input. It's always there, and every mod shares it.
191>
192> Your hook returns a tree to show something in the band, or `next(e)` to show nothing. A tree replaces what the mods [after yours](https://code.claude.com/docs/en/plugins/mods/events.md#the-order-mods-run-in) draw there. To keep theirs, put the result of `await next(e)` among the children of a [`Box`](#build-a-tree-from-elements) in your tree.
193>
194> To draw in the band, filter on `{ component: 'AbovePrompt' }`.
183195 
184A pane appears when your mod calls `$.ui.open` with an `id` you choose, as in `$.ui.open({ id: 'hello-tabs' })`. [Open a pane at the right time](#open-a-pane-at-the-right-time) covers the other fields and when a pane waits for a wider terminal.
185 
186To draw in your pane, filter on `{ component: 'Pane' }` and check that `e.requestId` is your `id`.
187 
188**Band above the prompt**
189 
190The band is a strip directly above the prompt input. It's always there, and every mod shares it.
191 
192Your hook returns a tree to show something in the band, or `next(e)` to show nothing. A tree replaces what the mods [after yours](https://code.claude.com/docs/en/plugins/mods/events.md#the-order-mods-run-in) draw there. To keep theirs, put the result of `await next(e)` among the children of a [`Box`](#build-a-tree-from-elements) in your tree.
193 
194To draw in the band, filter on `{ component: 'AbovePrompt' }`.
195 
196196### Change what Claude Code already draws
197197 
198198Claude Code draws most of its interface itself: messages, tool call rows, the spinner, and more. Each of those parts is a render site too, so a mod can restyle or replace it. To change one, filter your `ui.render` hook on its name from this table:
from line 210
210210 
211211At 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](https://code.claude.com/docs/en/plugins/mods/create.md#write-a-mod-yourself).
212212 
213**Change a detail**
213> [tab] Change a detail
214>
215> To keep Claude Code's drawing and change one part of it, pass `next` a copy of the event with changed `props`. This hook changes the text after the spinner's word:
216>
217> ```javascript theme={null}
218> on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
219> // Keep Claude Code's spinner, and change the text after its word
220> return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
221> })
222> ```
223>
224> The spinner keeps its animation and its word, and your text follows the word:
225>
226> ```text theme={null}
227> Thinking · tool calls: 2…
228> ```
214229 
215To keep Claude Code's drawing and change one part of it, pass `next` a copy of the event with changed `props`. This hook changes the text after the spinner's word:
230> [tab] Replace the drawing
231>
232> To draw something of your own in the site's place, return a tree and don't call `next`. This hook draws one line of text where the spinner would be:
233>
234> ```javascript theme={null}
235> on('ui.render', { component: 'Spinner' }, async ($, e) => {
236> const { Text } = $.ui.resolve(e)
237> // No call to next, so this line is drawn in the spinner's place
238> return Text({ children: ['Claude has made ' + calls + ' tool calls'] })
239> })
240> ```
241>
242> While Claude works, your line shows and Claude Code's spinner doesn't:
243>
244> ```text theme={null}
245> Claude has made 2 tool calls
246> ```
216247 
217```javascript theme={null}
218on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
219 // Keep Claude Code's spinner, and change the text after its word
220 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
221})
222```
248> [tab] Leave it alone
249>
250> To leave the site as Claude Code draws it, return `next(e)`. A hook often does that for some events and not others. This hook leaves the spinner alone until there's a call to count:
251>
252> ```javascript theme={null}
253> on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
254> // Nothing to show yet, so pass the event on unchanged
255> if (calls === 0) return next(e)
256> return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
257> })
258> ```
259>
260> Before the first tool call, the spinner looks the way it does without the mod:
261>
262> ```text theme={null}
263> Thinking…
264> ```
223265 
224The spinner keeps its animation and its word, and your text follows the word:
225 
226```text theme={null}
227Thinking · tool calls: 2…
228```
229 
230**Replace the drawing**
231 
232To draw something of your own in the site's place, return a tree and don't call `next`. This hook draws one line of text where the spinner would be:
233 
234```javascript theme={null}
235on('ui.render', { component: 'Spinner' }, async ($, e) => {
236 const { Text } = $.ui.resolve(e)
237 // No call to next, so this line is drawn in the spinner's place
238 return Text({ children: ['Claude has made ' + calls + ' tool calls'] })
239})
240```
241 
242While Claude works, your line shows and Claude Code's spinner doesn't:
243 
244```text theme={null}
245Claude has made 2 tool calls
246```
247 
248**Leave it alone**
249 
250To leave the site as Claude Code draws it, return `next(e)`. A hook often does that for some events and not others. This hook leaves the spinner alone until there's a call to count:
251 
252```javascript theme={null}
253on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
254 // Nothing to show yet, so pass the event on unchanged
255 if (calls === 0) return next(e)
256 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
257})
258```
259 
260Before the first tool call, the spinner looks the way it does without the mod:
261 
262```text theme={null}
263Thinking…
264```
265 
266266At these sites, `next(e)` returns a reference to Claude Code's drawing, `{ type: 'engine', ref }`, unless a mod that runs after yours returned a tree of its own. To change what's in that drawing, pass `next` a copy of the event with different props, as the **Change a detail** tab does. You can return the reference as it is, or place it in a `Box` beside elements of your own:
267267 
268268```javascript theme={null}
from line 353
353353 
354354Select a tab to see each of the most common elements and how the terminal draws it:
355355 
356**Text**
356> [tab] Text
357>
358> `Text` draws a string, with optional styling such as `bold` and `color`:
359>
360> ```javascript theme={null}
361> Text({ children: ['This is the first tab.'] })
362> ```
363>
364> ```text theme={null}
365> This is the first tab.
366> ```
357367 
358`Text` draws a string, with optional styling such as `bold` and `color`:
368> [tab] Box
369>
370> `Box` arranges what's inside it, in a row or a column. This one puts a button and a line of text side by side, two columns apart:
371>
372> ```javascript theme={null}
373> Box({
374> flexDirection: 'row',
375> columnGap: 2,
376> children: [
377> Button({ key: 'more', label: 'Add one', onPress: addOne }),
378> Text({ children: ['Count: 0'] }),
379> ],
380> })
381> ```
382>
383> ```text theme={null}
384> [ Add one ] Count: 0
385> ```
359386 
360```javascript theme={null}
361Text({ children: ['This is the first tab.'] })
362```
387> [tab] Button
388>
389> `Button` is a control the user can press. It runs your `onPress` callback. With `plain: true` it has no brackets and shows its hotkey:
390>
391> ```javascript theme={null}
392> Button({ key: 'more', label: 'Add one', onPress: addOne })
393> Button({ key: 'tab-one', label: 'One', hotkey: '1', plain: true, onPress: showTabOne })
394> ```
395>
396> ```text theme={null}
397> [ Add one ]
398> 1: One
399> ```
363400 
364```text theme={null}
365This is the first tab.
366```
367 
368**Box**
369 
370`Box` arranges what's inside it, in a row or a column. This one puts a button and a line of text side by side, two columns apart:
371 
372```javascript theme={null}
373Box({
374 flexDirection: 'row',
375 columnGap: 2,
376 children: [
377 Button({ key: 'more', label: 'Add one', onPress: addOne }),
378 Text({ children: ['Count: 0'] }),
379 ],
380})
381```
382 
383```text theme={null}
384[ Add one ] Count: 0
385```
386 
387**Button**
388 
389`Button` is a control the user can press. It runs your `onPress` callback. With `plain: true` it has no brackets and shows its hotkey:
390 
391```javascript theme={null}
392Button({ key: 'more', label: 'Add one', onPress: addOne })
393Button({ key: 'tab-one', label: 'One', hotkey: '1', plain: true, onPress: showTabOne })
394```
395 
396```text theme={null}
397[ Add one ]
3981: One
399```
400 
401**Input**
402 
403`Input` is a text field. It runs your `onSubmit` callback with the text when the user presses Enter:
404 
405```javascript theme={null}
406Input({
407 key: 'new-note',
408 label: 'Note',
409 placeholder: 'Type a note and press Enter',
410 value: '',
411 submitLabel: 'add',
412 onSubmit: addNote,
413})
414```
415 
416```text theme={null}
417Note: Type a note and press Enter
418```
419 
420Set `rows` to 2 or more for [a field that holds several lines](https://code.claude.com/docs/en/plugins/mods/gallery.md#input).
401> [tab] Input
402>
403> `Input` is a text field. It runs your `onSubmit` callback with the text when the user presses Enter:
404>
405> ```javascript theme={null}
406> Input({
407> key: 'new-note',
408> label: 'Note',
409> placeholder: 'Type a note and press Enter',
410> value: '',
411> submitLabel: 'add',
412> onSubmit: addNote,
413> })
414> ```
415>
416> ```text theme={null}
417> Note: Type a note and press Enter
418> ```
419>
420> Set `rows` to 2 or more for [a field that holds several lines](https://code.claude.com/docs/en/plugins/mods/gallery.md#input).
421421 
422422The [interface gallery](https://code.claude.com/docs/en/plugins/mods/gallery.md) has samples and screenshots of most elements. This table lists every element:
423423 
Feedback