UI API Guide
The UI API provides plugins with ways to interact with users through notifications, dialogs, status bar items, and more.
Overview
interface UIAPI {
// Notifications
showNotification(message: string, options?: NotificationOptions): void;
showError(message: string): void;
showWarning(message: string): void;
// Dialogs
showQuickPick<T>(items: QuickPickItem<T>[], options?: QuickPickOptions): Promise<T | undefined>;
showInputBox(options?: InputBoxOptions): Promise<string | undefined>;
showConfirmDialog(message: string, options?: ConfirmOptions): Promise<boolean>;
// Progress
withProgress<T>(options: ProgressOptions, task: ProgressTask<T>): Promise<T>;
// Status Bar
createStatusBarItem(options: StatusBarItemOptions): StatusBarItem;
// Theme
readonly theme: {
current: Theme;
onChange: Event<Theme>;
};
}Notifications
Show brief messages to users.
Basic Notifications
const { ui } = context;
// Simple notification
ui.showNotification('Operation completed!');
// With type
ui.showNotification('File saved', { type: 'success' });
ui.showNotification('Check your settings', { type: 'info' });
ui.showWarning('Disk space is low');
ui.showError('Failed to connect');Notification Options
interface NotificationOptions {
type?: 'info' | 'success' | 'warning' | 'error';
duration?: number; // Milliseconds (0 = persistent)
action?: {
label: string;
callback: () => void;
};
}With Action Button
ui.showNotification('Update available!', {
type: 'info',
duration: 0, // Persistent until dismissed
action: {
label: 'Install',
callback: () => {
installUpdate();
},
},
});Custom Duration
// Show for 10 seconds
ui.showNotification('Long message', { duration: 10000 });
// Show until dismissed
ui.showNotification('Important notice', { duration: 0 });Dialogs
Quick Pick (Selection Dialog)
Let users choose from a list of options.
interface QuickPickItem<T = string> {
label: string;
description?: string;
detail?: string;
value: T;
}
interface QuickPickOptions {
title?: string;
placeholder?: string;
canSelectMany?: boolean;
}Example: Simple Selection
const model = await ui.showQuickPick([
{ label: 'Claude 3 Opus', value: 'claude-3-opus' },
{ label: 'Claude 3 Sonnet', value: 'claude-3-sonnet' },
{ label: 'GPT-4', value: 'gpt-4' },
], {
title: 'Select a Model',
placeholder: 'Choose an AI model',
});
if (model) {
logger.info(`Selected: ${model}`);
}Example: Rich Items
const action = await ui.showQuickPick([
{
label: 'Export as PDF',
description: 'Download conversation',
detail: 'Exports the current thread as a formatted PDF document',
value: 'export-pdf',
},
{
label: 'Share Link',
description: 'Create shareable link',
detail: 'Generate a public link that others can view',
value: 'share-link',
},
{
label: 'Archive Thread',
description: 'Move to archive',
detail: 'Hides the thread from the main list',
value: 'archive',
},
]);Example: Multi-Select
const features = await ui.showQuickPick([
{ label: 'Syntax Highlighting', value: 'syntax' },
{ label: 'Auto-complete', value: 'autocomplete' },
{ label: 'Error Detection', value: 'errors' },
], {
title: 'Enable Features',
canSelectMany: true,
});
if (features) {
logger.info(`Enabled: ${features.join(', ')}`);
}Input Box
Get text input from users.
interface InputBoxOptions {
title?: string;
prompt?: string;
placeholder?: string;
value?: string;
password?: boolean;
validateInput?: (value: string) => string | undefined;
}Example: Simple Input
const name = await ui.showInputBox({
title: 'Enter Name',
prompt: 'What should we call you?',
placeholder: 'Your name',
});
if (name) {
ui.showNotification(`Hello, ${name}!`);
}Example: With Default Value
const timeout = await ui.showInputBox({
title: 'Request Timeout',
prompt: 'Enter timeout in seconds',
value: '30', // Pre-filled value
});Example: Password Input
const apiKey = await ui.showInputBox({
title: 'API Key',
prompt: 'Enter your API key',
password: true, // Masked input
});
if (apiKey) {
await storage.secrets.set('apiKey', apiKey);
}Example: With Validation
const port = await ui.showInputBox({
title: 'Server Port',
prompt: 'Enter a port number (1024-65535)',
value: '3000',
validateInput: (value) => {
const num = parseInt(value, 10);
if (isNaN(num)) {
return 'Must be a number';
}
if (num < 1024 || num > 65535) {
return 'Port must be between 1024 and 65535';
}
return undefined; // Valid
},
});Confirm Dialog
Get yes/no confirmation from users.
interface ConfirmOptions {
title?: string;
confirmLabel?: string;
cancelLabel?: string;
type?: 'info' | 'warning' | 'danger';
}Example: Simple Confirm
const confirmed = await ui.showConfirmDialog('Delete this thread?');
if (confirmed) {
await deleteThread(threadId);
}Example: Warning Dialog
const confirmed = await ui.showConfirmDialog(
'This will permanently delete all messages. Continue?',
{
title: 'Clear History',
type: 'warning',
confirmLabel: 'Delete All',
cancelLabel: 'Keep',
}
);Example: Danger Dialog
const confirmed = await ui.showConfirmDialog(
'This action cannot be undone. Your account and all data will be permanently deleted.',
{
title: 'Delete Account',
type: 'danger',
confirmLabel: 'Delete Forever',
}
);Progress Indicator
Show progress for long-running operations.
interface ProgressOptions {
title: string;
cancellable?: boolean;
}
interface ProgressReport {
increment?: number; // Percentage increase
message?: string; // Status message
}
type ProgressTask<T> = (
progress: { report: (value: ProgressReport) => void },
token: { isCancellationRequested: boolean }
) => Promise<T>;Example: Indeterminate Progress
const result = await ui.withProgress(
{ title: 'Processing...' },
async (progress) => {
progress.report({ message: 'Connecting...' });
await connect();
progress.report({ message: 'Downloading...' });
const data = await download();
progress.report({ message: 'Parsing...' });
return parse(data);
}
);Example: Determinate Progress
const results = await ui.withProgress(
{ title: 'Processing Files' },
async (progress) => {
const files = await getFiles();
const results = [];
for (let i = 0; i < files.length; i++) {
progress.report({
increment: 100 / files.length,
message: `Processing ${files[i].name}...`,
});
results.push(await processFile(files[i]));
}
return results;
}
);Example: Cancellable Progress
try {
const result = await ui.withProgress(
{
title: 'Uploading Files',
cancellable: true,
},
async (progress, token) => {
for (const file of files) {
// Check for cancellation
if (token.isCancellationRequested) {
throw new Error('Upload cancelled');
}
progress.report({ message: `Uploading ${file.name}...` });
await upload(file);
}
}
);
} catch (error) {
if (error.message === 'Upload cancelled') {
ui.showNotification('Upload cancelled', { type: 'info' });
} else {
ui.showError(`Upload failed: ${error.message}`);
}
}Status Bar
Add persistent items to the application status bar.
interface StatusBarItemOptions {
id: string;
alignment: 'left' | 'right';
priority?: number; // Higher = closer to edge
}
interface StatusBarItem extends Disposable {
text: string;
tooltip?: string;
command?: string;
show(): void;
hide(): void;
}Example: Simple Status Item
const statusItem = ui.createStatusBarItem({
id: 'my-plugin-status',
alignment: 'right',
priority: 100,
});
statusItem.text = 'Ready';
statusItem.tooltip = 'My Plugin is active';
statusItem.show();
// Update later
statusItem.text = 'Processing...';
// Clean up when done
statusItem.dispose();Example: With Command
// Register a command first
commands.register('myPlugin.showStats', () => {
ui.showNotification('Stats: 100 tokens used');
});
// Create status item that triggers the command
const statusItem = ui.createStatusBarItem({
id: 'token-counter',
alignment: 'right',
});
statusItem.text = 'Tokens: 100';
statusItem.tooltip = 'Click for details';
statusItem.command = 'myPlugin.showStats';
statusItem.show();Example: Real-time Updates
const statusItem = ui.createStatusBarItem({
id: 'live-counter',
alignment: 'right',
});
let count = 0;
events.on('chat.message.didReceive', (input) => {
if (input.response.usage) {
count += input.response.usage.totalTokens;
statusItem.text = `Tokens: ${count.toLocaleString()}`;
statusItem.tooltip = [
`Prompt: ${input.response.usage.promptTokens}`,
`Completion: ${input.response.usage.completionTokens}`,
].join('\n');
}
});
statusItem.text = 'Tokens: 0';
statusItem.show();Theme API
Access and react to theme changes.
interface Theme {
id: string;
name: string;
type: 'dark' | 'light';
}Example: Get Current Theme
const { ui, logger } = context;
const currentTheme = ui.theme.current;
logger.info(`Current theme: ${currentTheme.name} (${currentTheme.type})`);
if (currentTheme.type === 'dark') {
// Adjust for dark mode
}Example: React to Theme Changes
const disposable = ui.theme.onChange((theme) => {
logger.info(`Theme changed to: ${theme.name}`);
if (theme.type === 'dark') {
statusItem.text = '🌙 Dark Mode';
} else {
statusItem.text = '☀️ Light Mode';
}
});Best Practices
1. Keep Messages Concise
// Good
ui.showNotification('File saved');
// Avoid
ui.showNotification('The file has been successfully saved to the disk. You can now close the editor.');2. Use Appropriate Notification Types
ui.showNotification('Connected', { type: 'success' }); // Positive outcomes
ui.showNotification('Processing', { type: 'info' }); // Informational
ui.showWarning('Disk space low'); // Warnings
ui.showError('Connection failed'); // Errors only3. Provide Escape Hatches
Always allow users to cancel or dismiss:
// Input can be cancelled with ESC
const input = await ui.showInputBox({ ... });
if (!input) return; // User cancelled
// Confirm dialogs have cancel option
const confirmed = await ui.showConfirmDialog('Continue?');
if (!confirmed) return;4. Clean Up Status Items
export async function activate(context) {
const statusItem = ui.createStatusBarItem({ ... });
statusItem.show();
return {
dispose: () => {
statusItem.dispose(); // Always clean up
},
};
}5. Use Progress for Long Operations
// Bad: No feedback
await longOperation();
// Good: Shows progress
await ui.withProgress(
{ title: 'Processing...', cancellable: true },
async (progress, token) => {
await longOperation();
}
);