Галерея элементов интерфейса для модов
Посмотрите, какие элементы интерфейса может отрисовывать мод Claude Code: текст, кнопки, поля, Markdown, код и diff, — с примерами кода и снимками экрана терминала.
Мод строит свой интерфейс из элементов: текста, блоков, кнопок, полей и нескольких элементов, которые форматируют содержимое за вас. Примеры на этой странице показывают код, отрисовывающий элемент, и к большинству из них приложен снимок экрана с результатом на панели терминала, поэтому элемент можно выбрать по его внешнему виду.
Чтобы разобраться, как работает отрисовка, начните с раздела Отрисовка в интерфейсе. Основные свойства элементов и приложения, которые отрисовывают каждый из них, описаны в справочнике по элементам. В объявлениях типов перечислены все свойства.
Попробуйте пример
Примеры на этой странице — это фрагменты, а не готовые моды. Каждый из них — код одного элемента и всего, что в него вложено.
Чтобы посмотреть пример в своём терминале, создайте небольшой мод, следуя этим шагам, и вставьте в него пример. Мод добавляет команду /gallery, которая открывает панель и отрисовывает в ней пример. Панель — это боковая область рядом с транскриптом в широком полноэкранном терминале, а в остальных случаях — область в рамке над промптом.
Создайте мод
Создайте каталог gallery, а внутри него — каталоги .claude-plugin и hooks. Назначение файлов объясняется в разделе Создание мода.
Сохраните манифест как gallery/.claude-plugin/plugin.json:
{
"name": "gallery",
"version": "0.1.0",
"description": "Opens a pane that draws one sample",
"author": { "name": "Your Name" }
}
Укажите точку входа в gallery/hooks/hooks.json:
{
"modules": ["./register.js"]
}
Сохраните код как gallery/hooks/register.js. Он добавляет команду /gallery, которая открывает панель, и отрисовывает в ней Plain text:
// Stands in for your own callback in the samples that take one
const noop = () => {}
// The Select sample keeps its choice here
let picked = 'md'
// The Raster sample packs its cells with this function
const DEFAULT_COLOR = 0x01000000
function cellsOf(rows) {
const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
}
export function register(on) {
on('session.start', async ($, e, next) => {
await $.command.register({ name: 'gallery', description: 'Open the sample pane' })
return next(e)
})
on('command.run', { command: 'gallery' }, async ($) => {
await $.ui.open({ id: 'gallery', focus: true, closeOnEscape: true })
return {}
})
on('ui.render', { component: 'Pane' }, async ($, e, next) => {
if (e.requestId !== 'gallery') return next(e)
const { Box, Text, Button, Input, Select, Link, Markdown, Code, Raster, Svg } = $.ui.resolve(e)
// Replace the element after return with a sample
return Text({ children: ['Plain text'] })
})
}
Запустите мод
В оболочке запустите Claude Code из каталога, содержащего gallery:
claude --plugin-dir ./gallery
В промпте Claude Code выполните /gallery. Откроется панель с текстом Plain text.
Подставьте пример
Скопируйте пример с этой страницы. В register.js вставьте его вместо Text({ children: ['Plain text'] }), чтобы он шёл после return, и сохраните файл. Claude Code перезагружает модуль при каждом сохранении, поэтому снова выполните /gallery, чтобы увидеть новый пример.
Выберите элемент
Примеры сгруппированы по тому, что вы хотите вывести на экран:
- Вывод текста:
Text,MarkdownиLink - Вывод кода и изменений:
Code - Компоновка элементов:
Box - Ввод данных:
Button,InputиSelect - Рисование изображений:
Raster,Svg,ImageиClient
Вывод текста
Текст на экран выводят три элемента: Text — с вашим собственным оформлением, Markdown — для уже отформатированного содержимого и Link — для URL.
`Text`
Text отрисовывает строку с заданными вами стилями. В этом примере каждому стилю соответствует одна строка:
Box({
flexDirection: 'column',
children: [
Text({ children: ['Plain text'] }),
Text({ bold: true, children: ['bold'] }),
Text({ italic: true, children: ['italic'] }),
Text({ underline: true, children: ['underline'] }),
Text({ strikethrough: true, children: ['strikethrough'] }),
Text({ dimColor: true, children: ['dimColor'] }),
Text({ inverse: true, children: ['inverse'] }),
Text({ color: 'red', children: ["color: 'red'"] }),
Text({ backgroundColor: 'blue', children: ["backgroundColor: 'blue'"] }),
],
})
dimColor отрисовывает текст серым цветом. backgroundColor заливает фон только по ширине текста.
`Markdown`
Markdown форматирует текст так же, как форматируются ответы Claude. Передавайте содержимое в text, а не в children:
Markdown({
text: '## Release notes\n\nThis build has **two** fixes and one `flag`:\n\n- Faster start\n- Fewer prompts\n\n> Quoted text',
})
Заголовок отрисовывается жирным шрифтом без знаков #. Встроенный код отрисовывается цветом без обратных кавычек. Цитата отрисовывается курсивом с чертой слева.
`Link`
Link отрисовывает подпись, а за ней — её URL:
Link({ href: 'https://code.claude.com/docs', label: 'Claude Code docs' })
Терминал отрисовывает URL как текст после подписи. Откроется ли он по щелчку, зависит от терминала пользователя.
Вывод кода и изменений
Code отрисовывает исходный текст с подсветкой синтаксиса в цветах самого Claude Code или diff.
`Code`
Укажите язык в language или передайте path, чтобы Claude Code определил язык по нему. С startLine строки нумеруются начиная с этого числа:
Code({
language: 'javascript',
startLine: 1,
source: "const name = 'mods'\nconsole.log('hello ' + name)",
})
Цвета берутся из темы пользователя.
`Code` в виде diff
С format: 'diff' в source передаются один или несколько фрагментов (hunks) в формате unified diff:
Code({
format: 'diff',
source: '@@ -1,3 +1,3 @@\n # Mods\n-A mod is a plugin.\n+A mod is a plugin that runs code.\n Read on.',
})
Вместо строки @@ Claude Code отрисовывает номера строк. Если удалённая и добавленная строки похожи, изменившиеся слова подсвечиваются ярче.
Компоновка элементов
`Box`
Box располагает своё содержимое в строку или в столбец и может отрисовывать рамку. В этом примере над блоком в рамке размещается строка слов:
Box({
flexDirection: 'column',
gap: 1,
children: [
Box({
flexDirection: 'row',
columnGap: 4,
children: [Text({ children: ['a row'] }), Text({ children: ['of three'] }), Text({ children: ['items'] })],
}),
Box({
borderStyle: 'round',
paddingX: 1,
children: [Text({ children: ["borderStyle: 'round'"] })],
}),
],
})
Рамка растягивается на всю ширину панели.
Ввод данных
Button, Input и Select — это элементы управления: пользователь переходит между ними клавишей Tab и работает с тем, на котором находится фокус. Какие клавиши до них доходят, описано в разделе Фокус клавиатуры и горячие клавиши.
Если открыть панель с focus: true, она получит фокус клавиатуры. Вводимые буквы попадают в Input, только когда на нём фокус, поэтому добавьте autoFocus: true к полю, которое должно принимать ввод сразу после открытия панели.
`Button`
Кнопка вызывает onPress. В этом примере показаны кнопка по умолчанию, кнопка plain с горячей клавишей и приглушённая кнопка:
Box({
flexDirection: 'column',
children: [
Button({ key: 'save', label: 'Save', onPress: noop }),
Button({ key: 'next', label: 'Next', hotkey: 'n', plain: true, onPress: noop }),
Button({ key: 'skip', label: 'Skip', dimColor: true, onPress: noop }),
],
})
Кнопка с фокусом отрисовывается в инверсных цветах. Здесь пользователь дважды нажал Tab:
`Input`
Input — это однострочное текстовое поле, которое вызывает onSubmit, когда пользователь нажимает Enter:
Input({
key: 'title',
label: 'Title',
placeholder: 'Type a title and press Enter',
value: '',
submitLabel: 'save',
onSubmit: noop,
})
Без фокуса поле показывает подпись и текст-заполнитель:
С фокусом подпись становится жирной, появляется курсор, а после ⏎ отображается submitLabel:
Ввод заменяет текст-заполнитель:
`Select`
Select позволяет пользователю выбрать один из нескольких вариантов и вызывает onSelect со значением value выбранного варианта:
Select({
key: 'format',
label: 'Format',
value: picked,
options: [
{ value: 'md', label: 'Markdown' },
{ value: 'html', label: 'HTML' },
{ value: 'txt', label: 'Plain text' },
],
onSelect: (value) => {
picked = value
},
})
В закрытом состоянии он показывает подпись и текущий вариант:
В открытом состоянии он показывает список вариантов и выделяет один из них:
После того как пользователь выберет вариант, список закрывается:
Рисование изображений
`Raster`
Raster — это сетка цветных символьных ячеек для тепловой карты, спарклайна или игрового поля. Его отрисовывает терминал. В этом примере используется функция cellsOf из начального модуля, которая упаковывает ячейки в строку, принимаемую Raster. Подробнее см. в разделе Отрисовка сетки цветных ячеек:
Raster({
key: 'grid',
columns: 3,
rows: 2,
cells: cellsOf([
[['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
[['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
]),
})
Raster округляет каждый цвет до более ограниченной палитры, поэтому 0x2e7d32 отрисовывается как #337733.
`Svg`
Svg отрисовывает SVG-документ в приложении Desktop:
Svg({
alt: 'Three bars of rising height',
width: 120,
height: 60,
source:
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 60"><rect x="10" y="40" width="20" height="20" fill="#2e7d32"/><rect x="50" y="25" width="20" height="35" fill="#f9a825"/><rect x="90" y="5" width="20" height="55" fill="#c62828"/></svg>',
})
В терминале панель, которая возвращает только Svg, открывается пустой. Чтобы отрисовать там что-то другое, проверьте e.surface и верните другое дерево.
`Image` и `Client`
Для ещё двух элементов примеров здесь нет. Image отрисовывает PNG или необработанные пиксели в терминале. Client — это область, которую отрисовывает ваш второй файл, для анимации и ввода указателем. Их свойства перечислены в справочнике по элементам.
Если Claude Code не обнаружит, что терминал отрисовывает изображения протокола графики kitty с плейсхолдерами Unicode, пользователь увидит вместо картинки приглушённый текст alt элемента Image. Пишите текст alt, который понятен сам по себе. Обнаружение выполняется при запуске: оно завершается успешно в kitty 0.28 или новее и в Ghostty, как только терминал ответит на графический запрос Claude Code, и не срабатывает в следующих случаях:
- Другие терминалы: любой терминал, кроме этих двух, или терминал, который не отвечает на запрос.
- tmux и screen: сессия, запущенная внутри tmux или screen, в любом терминале, включая kitty и Ghostty.
- Фоновые сессии: любая фоновая сессия, из какого бы терминала к ней ни подключались.
Если пользователи вашего мода видят приглушённый текст в терминале, который всё же отрисовывает такие изображения с плейсхолдерами, они могут установить для CLAUDE_CODE_FORCE_TERMINAL_IMAGES значение 1, чтобы пропустить обнаружение. Внутри tmux или screen это не помогает: текст alt пропадает, а Claude Code отправляет изображение, не оборачивая его для сквозной передачи через tmux или screen.
Где ещё может отрисовывать мод
Все примеры отрисовываются на панели. Мод также может отрисовывать в других местах и вызывать Claude Code, чтобы тот что-то показал за него:
- Панель и полоса: Выбор места для отрисовки
- Собственные строки Claude Code, например индикатор загрузки: Изменение того, что уже отрисовывает Claude Code
- Всплывающее уведомление, строка состояния и строка лога: Показать что-то, не начиная ход
- Диалог с вопросом: Удержание вызова инструмента до решения пользователя
Дальнейшие шаги
- Отрисовка в интерфейсе: пошаговое создание панели с вкладками
- Тестирование отрисовки: нажатие ваших кнопок из теста
- Справочник по элементам: основные свойства каждого элемента и приложения, которые его отрисовывают