vscode-extension-uiux
VS Code Extension UI/UX Development
Build secure, beautiful, and accessible VS Code extensions following official guidelines.
Quick Reference
| UI Contribution | When to Use | Reference |
|---|---|---|
| Tree View | Hierarchical data, file explorers, outlines | tree-views.md |
| Webview | Custom UI beyond native API | webviews.md |
| Custom Editor | Replace default editor for file types | custom-editors.md |
| Sidebar View | Extension-specific panels | sidebars-panels.md |
Core Principles
1. Native First, Webview Last
Prefer VS Code's native APIs over webviews:
Native API (preferred) → Tree Views → Webview Views → Full Webviews (last resort)
Native components are lighter, auto-themed, and accessible by default.
2. Security by Default
Every webview MUST have:
- Content Security Policy with nonces
- Validated message passing
- Restricted local resource roots
See security.md for implementation patterns.
3. Theme Integration
Use VS Code color tokens for automatic theme support:
/* Always use CSS variables, never hardcode colors */
.my-element {
color: var(--vscode-foreground);
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
}
See theming.md for complete token reference.
4. Accessibility Requirements
- All interactive elements keyboard navigable
- ARIA labels on focusable elements (concise, important info first)
- Color contrast ratios meeting WCAG 2.1 AA
- Screen reader announcements for dynamic content
See accessibility.md for implementation guide.
Extension Anatomy
my-extension/
├── package.json # Manifest: activation, contributions, dependencies
├── src/
│ ├── extension.ts # Entry: activate/deactivate functions
│ ├── providers/ # Tree, webview, editor providers
│ └── views/ # Webview HTML/CSS/JS
├── media/ # Icons, images, webview assets
└── resources/ # Static files (templates, schemas)
package.json Contributions
{
"contributes": {
"viewsContainers": {
"activitybar": [{
"id": "myExtension",
"title": "My Extension",
"icon": "media/icon.svg"
}]
},
"views": {
"myExtension": [{
"id": "myTreeView",
"name": "Explorer",
"icon": "media/tree.svg"
}]
},
"commands": [{
"command": "myExtension.refresh",
"title": "Refresh",
"icon": "$(refresh)"
}],
"menus": {
"view/title": [{
"command": "myExtension.refresh",
"when": "view == myTreeView",
"group": "navigation"
}]
}
}
}
UI Component Selection Guide
Need to display...
│
├─ Hierarchical data? → Tree View
│ └─ With custom rendering? → Tree View + TreeItem.iconPath/description
│
├─ Simple list? → Tree View (flat)
│ └─ With actions? → TreeItem.command + context menu
│
├─ Form/settings UI? → Webview View (sidebar) or Settings contribution
│
├─ Rich content preview? → Webview Panel
│
├─ Custom file editor? → Custom Editor
│ └─ Text-based? → CustomTextEditorProvider
│ └─ Binary/complex? → CustomEditorProvider
│
├─ Status/info? → Status Bar Item
│
└─ Quick input? → QuickPick / InputBox API
Common Patterns
Tree View with Actions
// Provider
class MyTreeProvider implements vscode.TreeDataProvider<MyItem> {
private _onDidChangeTreeData = new vscode.EventEmitter<MyItem | undefined>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
getTreeItem(element: MyItem): vscode.TreeItem {
const item = new vscode.TreeItem(element.label);
item.iconPath = new vscode.ThemeIcon('file');
item.contextValue = 'myItem'; // For context menu filtering
item.command = { command: 'myExtension.openItem', title: 'Open', arguments: [element] };
return item;
}
getChildren(element?: MyItem): MyItem[] {
return element ? element.children : this.rootItems;
}
}
// Registration
const provider = new MyTreeProvider();
vscode.window.registerTreeDataProvider('myTreeView', provider);
vscode.commands.registerCommand('myExtension.refresh', () => provider.refresh());
Secure Webview Panel
function createWebviewPanel(context: vscode.ExtensionContext) {
const panel = vscode.window.createWebviewPanel(
'myWebview',
'My Panel',
vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, 'media')
]
}
);
panel.webview.html = getWebviewContent(panel.webview, context.extensionUri);
// Handle messages from webview
panel.webview.onDidReceiveMessage(
message => {
switch (message.command) {
case 'alert':
vscode.window.showInformationMessage(message.text);
return;
}
},
undefined,
context.subscriptions
);
}
function getWebviewContent(webview: vscode.Webview, extensionUri: vscode.Uri): string {
const nonce = getNonce();
const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'style.css'));
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'main.js'));
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
style-src ${webview.cspSource};
script-src 'nonce-${nonce}';
img-src ${webview.cspSource} https:;
font-src ${webview.cspSource};
">
<link href="${styleUri}" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
}
function getNonce(): string {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
Message Passing (Extension ↔ Webview)
// Extension side
panel.webview.postMessage({ type: 'update', data: myData });
panel.webview.onDidReceiveMessage(message => {
// ALWAYS validate message structure
if (typeof message !== 'object' || !message.type) return;
switch (message.type) {
case 'request':
// Handle request
break;
}
});
// Webview side (main.js)
const vscode = acquireVsCodeApi();
// Restore state on load
const previousState = vscode.getState();
if (previousState) {
restoreState(previousState);
}
window.addEventListener('message', event => {
const message = event.data;
switch (message.type) {
case 'update':
updateUI(message.data);
// Persist state
vscode.setState({ ...vscode.getState(), data: message.data });
break;
}
});
// Send message to extension
vscode.postMessage({ type: 'request', payload: data });
UI Toolkit Options (Post-2025)
The official @vscode/webview-ui-toolkit was deprecated January 2025. Alternatives:
| Option | Pros | Cons |
|---|---|---|
| vscrui | React components, maintained | React dependency |
| Custom CSS | Full control, no deps | More work, theme sync |
| Codicons + CSS vars | Native look, lightweight | Limited components |
For most cases, use VS Code CSS variables + Codicons for native appearance.
Checklist Before Publishing
- CSP configured for all webviews
- All colors use VS Code CSS variables
- Keyboard navigation works throughout
- ARIA labels on all interactive elements
- Message validation on both sides
- localResourceRoots restricted appropriately
- Extension activates only when needed (activation events)
- Icons use proper formats (SVG preferred)
- Tested with light, dark, and high contrast themes
More from kcchien/skills
excalidraw
Create professional diagrams and visualizations using Excalidraw JSON format. Specialized for IT architecture diagrams, flowcharts, network topology, system design, microservices, ER diagrams, and state machines. Includes curated color palettes and visual styles. Use when working with .excalidraw files, or when user mentions diagrams, flowcharts, architecture visualization, or drawing. Delegates to subagents to prevent context exhaustion from verbose JSON.
5industrial-expert
>
4crisp-reading
>
2agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction.
2adapt
Adapt designs to work across different screen sizes, devices, contexts, or platforms. Ensures consistent experience across varied environments.
1animate
Review a feature and enhance it with purposeful animations, micro-interactions, and motion effects that improve usability and delight.
1