Views are the building blocks of VS Code’s sidebar, panel, and auxiliary bar. They provide custom UI for displaying hierarchical data, trees, webviews, and other content.
Understanding Views
A view is a UI component that:
Lives inside a view container (sidebar, panel, or auxiliary bar)
Can display trees, lists, webviews, or custom content
Has a title, icon, and optional actions
Can be shown, hidden, moved, or resized by users
Integrates with VS Code’s layout system
Views are registered through the IViewsRegistry and managed by the IViewDescriptorService.
View Architecture
View Hierarchy
View Container (e.g., Explorer)
├── View 1 (e.g., Folder Explorer)
├── View 2 (e.g., Outline)
└── View 3 (e.g., Timeline)
View Locations
export enum ViewContainerLocation {
Sidebar,
Panel,
AuxiliaryBar
}
Sidebar (ViewContainerLocation.Sidebar)
Primary location for navigation and exploration
Examples: Explorer, Search, Source Control, Extensions
Typically on the left (or right if configured)
Panel (ViewContainerLocation.Panel)
Secondary location for tools and output
Examples: Terminal, Problems, Output, Debug Console
Typically at the bottom
Auxiliary Bar (ViewContainerLocation.AuxiliaryBar)
Additional sidebar for extra views
Can be placed opposite the main sidebar
For overflow or secondary content
Creating a View Container
Step 1: Register the View Container
import { Registry } from 'vs/platform/registry/common/platform' ;
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors' ;
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer' ;
import {
Extensions as ViewExtensions ,
IViewContainersRegistry ,
ViewContainerLocation
} from 'vs/workbench/common/views' ;
const MY_VIEW_CONTAINER_ID = 'myExtension.myViewContainer' ;
const MY_VIEW_CONTAINER = Registry . as < IViewContainersRegistry >( ViewExtensions . ViewContainersRegistry )
. registerViewContainer ({
id: MY_VIEW_CONTAINER_ID ,
title: 'My View Container' ,
icon: Codicon . symbolColor , // Icon for activity bar
order: 5 , // Position in activity bar
ctorDescriptor: new SyncDescriptor (
ViewPaneContainer ,
[ MY_VIEW_CONTAINER_ID , { mergeViewWithContainerWhenSingleView: true }]
),
storageId: MY_VIEW_CONTAINER_ID , // For persisting state
hideIfEmpty: true // Hide when no views are visible
}, ViewContainerLocation . Sidebar );
Step 2: Create a View Pane
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane' ;
import { IViewDescriptorService } from 'vs/workbench/services/views/common/viewDescriptorService' ;
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView' ;
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding' ;
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey' ;
import { IOpenerService } from 'vs/platform/opener/common/opener' ;
import { IThemeService } from 'vs/platform/theme/common/themeService' ;
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
export class MyViewPane extends ViewPane {
static readonly ID = 'myExtension.myView' ;
static readonly TITLE = 'My View' ;
constructor (
options : IViewletViewOptions ,
@ IKeybindingService keybindingService : IKeybindingService ,
@ IContextMenuService contextMenuService : IContextMenuService ,
@ IConfigurationService configurationService : IConfigurationService ,
@ IContextKeyService contextKeyService : IContextKeyService ,
@ IViewDescriptorService viewDescriptorService : IViewDescriptorService ,
@ IInstantiationService instantiationService : IInstantiationService ,
@ IOpenerService openerService : IOpenerService ,
@ IThemeService themeService : IThemeService ,
@ ITelemetryService telemetryService : ITelemetryService ,
) {
super ( options , keybindingService , contextMenuService , configurationService , contextKeyService , viewDescriptorService , instantiationService , openerService , themeService , telemetryService );
}
protected override renderBody ( container : HTMLElement ) : void {
super . renderBody ( container );
// Create your view content
const content = document . createElement ( 'div' );
content . className = 'my-view-content' ;
content . textContent = 'Hello from my custom view!' ;
container . appendChild ( content );
}
protected override layoutBody ( height : number , width : number ) : void {
super . layoutBody ( height , width );
// Handle layout changes
}
}
Step 3: Register the View
import { Extensions as ViewExtensions , IViewsRegistry } from 'vs/workbench/common/views' ;
const viewsRegistry = Registry . as < IViewsRegistry >( ViewExtensions . ViewsRegistry );
viewsRegistry . registerViews ([{
id: MyViewPane . ID ,
name: MyViewPane . TITLE ,
containerIcon: Codicon . symbolColor ,
canToggleVisibility: true ,
canMoveView: true ,
ctorDescriptor: new SyncDescriptor ( MyViewPane ),
order: 1 ,
when: undefined // Context key expression for when to show
}], MY_VIEW_CONTAINER );
Creating a Tree View
For hierarchical data, use a tree view:
import { TreeView } from 'vs/workbench/browser/parts/views/treeView' ;
import { ITreeViewDataProvider } from 'vs/workbench/common/views' ;
export class MyTreeViewPane extends ViewPane {
private treeView : TreeView | undefined ;
protected override renderBody ( container : HTMLElement ) : void {
super . renderBody ( container );
// Create tree view
this . treeView = this . instantiationService . createInstance (
TreeView ,
MyTreeViewPane . ID ,
this . title
);
// Set data provider
this . treeView . dataProvider = new MyDataProvider ();
// Render tree
this . treeView . setInput ( undefined );
this . _register ( this . treeView );
const treeContainer = container . appendChild ( $ ( '.tree-explorer-viewlet-tree-view' ));
treeContainer . appendChild ( this . treeView . domNode );
}
protected override layoutBody ( height : number , width : number ) : void {
super . layoutBody ( height , width );
this . treeView ?. layout ( height , width );
}
}
class MyDataProvider implements ITreeViewDataProvider {
async getChildren ( element ?: any ) : Promise < any []> {
if ( ! element ) {
// Return root elements
return [
{ label: 'Item 1' , id: '1' },
{ label: 'Item 2' , id: '2' }
];
}
// Return children of element
return [];
}
async getTreeItem ( element : any ) : Promise < any > {
return {
label: element . label ,
collapsibleState: TreeItemCollapsibleState . None
};
}
}
View Pane Container
The ViewPaneContainer manages multiple view panes:
export interface IViewPaneContainerOptions extends IPaneViewOptions {
mergeViewWithContainerWhenSingleView: boolean;
}
export abstract class ViewPaneContainer extends Composite implements IViewPaneContainer {
private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
private readonly _onDidAddViews = this._register(new Emitter<IView[]>());
readonly onDidAddViews = this._onDidAddViews.event;
private readonly _onDidRemoveViews = this._register(new Emitter<IView[]>());
readonly onDidRemoveViews = this._onDidRemoveViews.event;
private paneItems: IViewPaneItem[] = [];
private paneview?: PaneView;
constructor(
id: string,
options: IViewPaneContainerOptions,
@IInstantiationService protected instantiationService: IInstantiationService,
@IConfigurationService protected configurationService: IConfigurationService,
@IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@ITelemetryService telemetryService: ITelemetryService,
@IExtensionService protected extensionService: IExtensionService,
@IThemeService themeService: IThemeService,
@IStorageService protected storageService: IStorageService,
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService
) {
super(id, telemetryService, themeService, storageService);
}
}
View Descriptor Service
The ViewDescriptorService manages view registration and location:
export class ViewDescriptorService extends Disposable implements IViewDescriptorService {
private readonly _onDidChangeContainer: Emitter<{
views: IViewDescriptor[];
from: ViewContainer;
to: ViewContainer
}>;
readonly onDidChangeContainer: Event<{
views: IViewDescriptor[];
from: ViewContainer;
to: ViewContainer
}>;
private readonly _onDidChangeLocation: Emitter<{
views: IViewDescriptor[];
from: ViewContainerLocation;
to: ViewContainerLocation
}>;
readonly onDidChangeLocation: Event<{
views: IViewDescriptor[];
from: ViewContainerLocation;
to: ViewContainerLocation
}>;
// Get view container location
getViewContainerLocation(container: ViewContainer): ViewContainerLocation | null;
// Move view to different container
moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void;
// Move view to different container
moveViewsToContainer(views: IViewDescriptor[], container: ViewContainer): void;
}
Real-World Example: Debug Views
From the Debug contribution:
// Register debug view container
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry)
.registerViewContainer({
id: VIEWLET_ID,
title: nls.localize('run', "Run"),
icon: icons.debugViewIcon,
order: 2,
ctorDescriptor: new SyncDescriptor(DebugViewPaneContainer),
storageId: 'workbench.debug.viewlet.state',
hideIfEmpty: true
}, ViewContainerLocation.Sidebar, { doNotRegisterOpenCommand: true });
// Register individual views
Register.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([{
id: VARIABLES_VIEW_ID,
name: nls.localize('variables', "Variables"),
containerIcon: icons.debugViewIcon,
canToggleVisibility: true,
canMoveView: true,
ctorDescriptor: new SyncDescriptor(VariablesView),
order: 10,
when: CONTEXT_DEBUG_UX.isEqualTo('default')
}], VIEW_CONTAINER);
View Actions
Add actions to view titles:
import { MenuRegistry , MenuId } from 'vs/platform/actions/common/actions' ;
// Register view title action
MenuRegistry . appendMenuItem ( MenuId . ViewTitle , {
command: {
id: 'myExtension.refreshView' ,
title: 'Refresh' ,
icon: Codicon . refresh
},
group: 'navigation' ,
when: ContextKeyExpr . equals ( 'view' , MyViewPane . ID )
});
View Context
Use context keys to control view visibility:
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey' ;
// Register view with context
viewsRegistry . registerViews ([{
id: MyViewPane . ID ,
name: MyViewPane . TITLE ,
ctorDescriptor: new SyncDescriptor ( MyViewPane ),
when: ContextKeyExpr . and (
ContextKeyExpr . equals ( 'config.myExtension.enabled' , true ),
ContextKeyExpr . notEquals ( 'workbenchState' , 'empty' )
)
}], MY_VIEW_CONTAINER );
Webview Views
For custom HTML/CSS/JS content:
import { WebviewPane } from 'vs/workbench/contrib/webviewPanel/browser/webviewPanel' ;
export class MyWebviewPane extends ViewPane {
private webview : Webview | undefined ;
protected override renderBody ( container : HTMLElement ) : void {
super . renderBody ( container );
this . webview = this . _register ( this . instantiationService . createInstance (
Webview ,
{ /* webview options */ }
));
// Set HTML content
this . webview . setHtml ( `
<!DOCTYPE html>
<html>
<body>
<h1>Custom Webview Content</h1>
<button onclick="alert('Hello!')">Click Me</button>
</body>
</html>
` );
container . appendChild ( this . webview . container );
}
}
Best Practices
View Development Best Practices
Lazy Loading : Use when context keys to only show views when relevant
Proper Disposal : Always dispose resources in dispose() method
Responsive Layout : Handle layoutBody() for proper resizing
State Persistence : Use storage service to save view state
Performance : Virtualize large lists/trees for better performance
Accessibility : Ensure keyboard navigation and screen reader support
Context Keys : Use specific context keys for view-specific commands
Error Handling : Show user-friendly messages for errors
Debugging Views
Use these techniques to debug views:
// Log view lifecycle
protected override renderBody ( container : HTMLElement ): void {
super . renderBody ( container );
this . logService . info ( ` ${ MyViewPane . ID } : renderBody called` );
}
protected override layoutBody ( height : number , width : number ): void {
super . layoutBody ( height , width );
this . logService . info ( ` ${ MyViewPane . ID } : layout ${ width } x ${ height } ` );
}
override dispose (): void {
this . logService . info ( ` ${ MyViewPane . ID } : disposed` );
super . dispose ();
}
Next Steps
Tree Views Learn more about creating tree views
Webviews Create rich HTML/CSS/JS views with webviews
Commands Add commands and actions to your views
Themes Style your views with theme colors