Webview UI Toolkit for Visual Studio Code

repository·main·Indexed 24 days ago

https://github.com/microsoft/vscode-webview-ui-toolkit

A framework-agnostic component library of web components for building webview-based extensions in Visual Studio Code. It provides components like vscode-button, vscode-checkbox, and vscode-data-grid that automatically handle theming, accessibility, and design consistency to match the native VS Code UI. Note: This toolkit is scheduled for deprecation on January 1, 2025.

Tokens
26.6K
Snippets
59
Records
139
Agent score
84%

What's inside @vscode/webview-ui-toolkit

  1. Overview of the Webview UI Toolkit

    main

    The Webview UI Toolkit is a component library designed for building webview-based extensions in Visual Studio Code. It provides web components that follow the VS Code design language, ensuring a consistent look and feel with the editor.

    Key features include:

    • VS Code Design Language: Components match the editor's visual style.
    • Automatic Theming: Components automatically adapt to the user's current editor color theme.
    • Framework Agnostic: As a set of web components, it can be used with React, Vue, Svelte, or vanilla JavaScript.
    • Accessibility: Components include web-standard compliant ARIA labels and keyboard navigation support.
  2. Available Webview UI Toolkit components

    main

    The toolkit provides a collection of web components designed to match the Visual Studio Code look and feel. You can use these components in your VS Code webview.

    If you are using React, refer to the React documentation for specific instructions on using the React toolkit components.

    | Component       | Doc Link                                                                                     |
    | --------------- | --------------------------------------------------------------------------------------------- |
    | `badge`         | [Badge Documentation](../src/badge/README.md)                                                 |
    | `button`        | [Button Documentation](../src/button/README.md)                                               |
    | `checkbox`      | [Checkbox Documentation](../src/checkbox/README.md)                                           |
    | `data-grid`     | [Data Grid Documentation](../src/data-grid/README.md)                                         |
    | `divider`       | [Divider Documentation](../src/divider/README.md)                                             |
    | `dropdown`      | [Dropdown Documentation](../src/dropdown/README.md)                                           |
    | `link`          | [Link Documentation](../src/link/README.md)                                                   |
    | `option`        | [Option Documentation](../src/option/README.md)                                               |
    | `panels`        | [Panels Documentation](../src/panels/README.md)                                                |
    | `progress-ring` | [Progress Ring Documentation](../src/progress-ring/README.md)                               |
    | `radio`         | [Radio Documentation](../src/radio/README.md)                                                 |
    | `radio-group`   | [Radio Group Documentation](../src/radio-group/README.md)                                    |
    | `tag`           | [Tag Documentation](../src/tag/README.md)                                                     |
    | `text-area`     | [Text Area Documentation](../src/text-area/README.md)                                       |
    | `text-field`    | [Text Field Documentation](../src/text-field/README.md)                                      |
  3. How Visual Studio Code Panels work

    main

    The vscode-panels component is a web component implementation of a tab interface. It uses three interconnected components to manage and display content:

    1. <vscode-panels>: The top-level container element that manages the state and active tab.
    2. <vscode-panel-tab>: The clickable tab element that represents a specific view.
    3. <vscode-panel-view>: The container that holds the actual content associated with a specific tab.

    To function correctly, you should associate a <vscode-panel-tab> with a <vscode-panel-view>. While not strictly required for basic rendering, it is a best practice to assign unique id attributes to both tabs and views to facilitate styling and programmatic control via the activeid attribute.

  4. Create accessible icon-only buttons

    main

    When using appearance="icon" without text, the button is not semantically accessible by default. While an aria-label="Icon Button" is automatically provided, you should always override it with a descriptive aria-label that fits the context (e.g., "Confirm" or "Delete") to ensure screen reader users understand the action.

    <!-- Note: Using Visual Studio Code Codicon Library -->
    <vscode-button appearance="icon" aria-label="Confirm">
      <span class="codicon codicon-check"></span>
    </vscode-button>
  5. How the Visual Studio Code Data Grid components work together

    main

    The vscode-data-grid is a tabular layout component composed of three hierarchical elements:

    1. <vscode-data-grid>: The top-level container that manages the grid structure.
    2. <vscode-data-grid-row>: Represents a single row of data (a record) or a header row.
    3. <vscode-data-grid-cell>: Represents an individual cell of data within a row.

    Use this component to display complex data sets with secondary information rather than using it as a simple list.

  6. Handle keystrokes in React input components using `onInput`

    main

    Because the React components are wrappers for web components, they follow the native browser event model rather than React's overridden behavior.

    For input components like VSCodeTextField or VSCodeTextArea:

    • Use onInput to handle updates on every keystroke.
    • Use onChange to handle updates only when the element loses focus.

    If you need to update state immediately as the user types, you must use onInput.

    import { VSCodeTextField } from '@vscode/webview-ui-toolkit/react';
    
    function SomeComponent() {
      const [value, setValue] = useState('');
      
      return <VSCodeTextField value={value} onInput={e => setValue(e.target.value)} />
    }
  7. Implement a Webview Panel class

    main

    To manage a webview, create a dedicated class (e.g., HelloWorldPanel) to handle the lifecycle, state, and HTML content. This prevents resource leaks and centralizes webview logic.

    Key responsibilities of the class:

    • Singleton Management: Use a static currentPanel property to track if a panel is already open.
    • Rendering: Use vscode.window.createWebviewPanel to create the panel and panel.reveal() to show it if it already exists.
    • Cleanup: Implement a dispose() method that clears the static reference, disposes of the vscode.WebviewPanel, and iterates through any internal _disposables to clean up resources.
    • Lifecycle Hook: Listen to this._panel.onDidDispose in the constructor to trigger the class's dispose() method when the user closes the webview tab.
    • Content Injection: Use a private method (e.g., _getWebviewContent()) to return the HTML string, which is then assigned to this._panel.webview.html.
    import * as vscode from "vscode";
    
    export class HelloWorldPanel {
      public static currentPanel: HelloWorldPanel | undefined;
      private readonly _panel: vscode.WebviewPanel;
      private _disposables: vscode.Disposable[] = [];
    
      private constructor(panel: vscode.WebviewPanel) {
        this._panel = panel;
        this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
        this._panel.webview.html = this._getWebviewContent();
      }
    
      public static render() {
        if (HelloWorldPanel.currentPanel) {
          HelloWorldPanel.currentPanel._panel.reveal(vscode.ViewColumn.One);
        } else {
          const panel = vscode.window.createWebviewPanel(
            "hello-world",
            "Hello World",
            vscode.ViewColumn.One,
            {}
          );
          HelloWorldPanel.currentPanel = new HelloWorldPanel(panel);
        }
      }
    
      public dispose() {
        HelloWorldPanel.currentPanel = undefined;
        this._panel.dispose();
        while (this._disposables.length) {
          const disposable = this._disposables.pop();
          if (disposable) {
            disposable.dispose();
          }
        }
      }
    
      private _getWebviewContent() {
        return /*html*/ `
          <!DOCTYPE html>
          <html lang="en">
            <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Hello World!</title>
            </head>
            <body>
              <h1>Hello World!</h1>
            </body>
          </html>
        `;
      }
    }
  8. Configure TypeScript for Webview development

    main

    When developing webviews, you may encounter type errors related to DOM elements or the VS Code webview API. To resolve these:

    1. Enable DOM types: Update your tsconfig.json to include DOM in the lib array.
    2. Install Webview types: Install @types/vscode-webview as a development dependency to provide type definitions for the webview environment.

    If type errors persist after these changes, restart the TypeScript language server using the command TypeScript: Restart TS server in the VS Code command palette.

    // Update tsconfig.json
    {
      "compilerOptions": {
        "lib": ["ES2020", "DOM"]
      }
    }
    
    // Install webview types
    npm install --save-dev @types/vscode-webview
  9. Implement an editable Data Grid

    main

    The @vscode/webview-ui-toolkit and its underlying framework, FAST, do not currently provide first-party APIs for enabling interactivity or editability within the vscode-data-grid component.

    To implement an editable grid, you must use a workaround. A reference implementation for making the vscode-data-grid component editable is provided in the official sample extension repository.

    https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/default/editable-data-grid
  10. Use the Progress Ring component

    main

    The vscode-progress-ring component is used to indicate an indeterminate loading state. It displays a looping animation for situations where the wait time is unspecified.

    Best Practices

    When to use:

    • Indicate indeterminate progress while an extension waits for something to load or to finish executing.

    When NOT to use:

    • Do not use a Progress Ring to indicate user progress on a multi-step task (use a progress bar for determinate progress instead).
    • Do not use multiple Progress Rings in close proximity. If multiple must be used, ensure they are placed in clearly defined, distinct sections of the extension view.
    <vscode-progress-ring></vscode-progress-ring>
  11. Configure esbuild for bundling VS Code extensions

    main

    VS Code extensions require bundling for a Node/CommonJS-based environment. This guide uses esbuild to compile src/extension.ts into out/extension.js.

    1. Install esbuild as a dev dependency (pinned to v0.16.17 to avoid breaking changes in v0.17.0):
    npm i --save-dev esbuild@0.16.17
    1. Create an esbuild.js script in your project root to handle bundling, minification (for production), and sourcemaps.

    2. Update package.json scripts to integrate with the build process:

    "scripts": {
      "vscode:prepublish": "npm run package",
      "compile": "node ./esbuild.js",
      "package": "NODE_ENV=production node ./esbuild.js",
      "watch": "node ./esbuild.js --watch"
    }
    1. (Optional) For a better debugging experience, configure the problemMatcher in .vscode/tasks.json to use $esbuild-watch. You may need to install the esbuild-problem-matchers extension from the VS Code Marketplace.
    npm i --save-dev esbuild@0.16.17
    
    // package.json snippet
    "scripts": {
      "vscode:prepublish": "npm run package",
      "compile": "node ./esbuild.js",
      "package": "NODE_ENV=production node ./esbuild.js",
      "watch": "node ./esbuild.js --watch"
    }