--- name: accessibility description: Accessibility guidelines for VS Code features — covers accessibility help dialogs, accessible views, verbosity settings, accessibility signals, ARIA alerts/status announcements, keyboard navigation, and ARIA labels/roles. Applies to both new interactive UI surfaces and updates to existing features. Use when creating new UI or updating existing UI features. --- When adding a **new interactive UI surface** to VS Code — a panel, view, widget, editor overlay, dialog, or any rich focusable component the user interacts with — you **must** provide three accessibility components (if they do not already exist for the feature): 1. **An Accessibility Help Dialog** — opened via the accessibility help keybinding when the feature has focus. 2. **An Accessible View** — a plain-text read-only editor that presents the feature's content to screen reader users (when the feature displays non-trivial visual content). 3. **An Accessibility Verbosity Setting** — a boolean setting that controls whether the "open accessibility help" hint is announced. Examples of existing features that have all three: the **terminal**, **chat panel**, **notebook**, **diff editor**, **inline completions**, **comments**, **debug REPL**, **hover**, and **notifications**. Features with only a help dialog (no accessible view) include **find widgets**, **source control input**, **keybindings editor**, **problems panel**, and **walkthroughs**. Sections 4–7 below (signals, ARIA announcements, keyboard navigation, ARIA labels) apply more broadly to **any UI change**, including modifications to existing features. When **updating an existing feature** — for example, adding new commands, keyboard shortcuts, or interactive capabilities — you must also update the feature's existing accessibility help dialog (`provideContent()`) to document the new functionality. Screen reader users rely on the help dialog as the primary way to discover available actions. --- ## 1. Accessibility Help Dialog An accessibility help dialog tells the user what the feature does, which keyboard shortcuts are available, and how to interact with it via a screen reader. ### Steps 1. **Create a class implementing `IAccessibleViewImplementation`** with `type = AccessibleViewType.Help`. - Set a `priority` (higher = shown first when multiple providers match). - Set `when` to a `ContextKeyExpression` that matches when the feature is focused. - `getProvider(accessor)` returns an `AccessibleContentProvider`. 2. **Create a content-provider class** implementing `IAccessibleViewContentProvider`. - `id` — add a new entry in the `AccessibleViewProviderId` enum in `src/vs/platform/accessibility/browser/accessibleView.ts`. - `verbositySettingKey` — reference the new `AccessibilityVerbositySettingId` entry (see §3). - `options` — `{ type: AccessibleViewType.Help }`. - `provideContent()` — return localized, multi-line help text. 3. **Implement `onClose()`** to restore focus to whatever element was focused before the help dialog opened. This ensures keyboard users and screen reader users return to their previous context. 4. **Register** the implementation: ```ts AccessibleViewRegistry.register(new MyFeatureAccessibilityHelp()); ``` in the feature's `*.contribution.ts` file. ### Example skeleton The simplest approach is to return an `AccessibleContentProvider` directly from `getProvider()`. This is the most common pattern in the codebase (used by chat, inline chat, quick chat, etc.): ```ts import { AccessibleViewType, AccessibleContentProvider, AccessibleViewProviderId } from '…/accessibleView.js'; import { IAccessibleViewImplementation } from '…/accessibleViewRegistry.js'; import { AccessibilityVerbositySettingId } from '…/accessibilityConfiguration.js'; import { AccessibleViewType, AccessibleContentProvider, AccessibleViewProviderId, IAccessibleViewContentProvider, IAccessibleViewOptions } from '../../../../platform/accessibility/browser/accessibleView.js'; import { IAccessibleViewImplementation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { AccessibilityVerbositySettingId } from '../../../../platform/accessibility/common/accessibilityConfiguration.js'; export class MyFeatureAccessibilityHelp implements IAccessibleViewImplementation { readonly priority = 100; readonly name = 'my-feature'; readonly type = AccessibleViewType.Help; readonly when = MyFeatureContextKeys.isFocused; getProvider(accessor: ServicesAccessor) { const helpText = [ localize('myFeature.help.overview', "You are in My Feature. …"), localize('myFeature.help.key1', "- {0}: Do something", ''), ].join('\n'); return new AccessibleContentProvider( AccessibleViewProviderId.MyFeature, { type: AccessibleViewType.Help }, () => helpText, () => { /* onClose — refocus whatever was focused before */ }, AccessibilityVerbositySettingId.MyFeature, ); } } ``` Alternatively, if the provider needs injected services or must track state (e.g., storing a reference to the previously focused element), create a custom class that extends `Disposable` and implements `IAccessibleViewContentProvider`, then instantiate it via `IInstantiationService` (see `CommentsAccessibilityHelpProvider` for an example): ```ts class MyFeatureAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider { readonly id = AccessibleViewProviderId.MyFeature; readonly verbositySettingKey = AccessibilityVerbositySettingId.MyFeature; readonly options: IAccessibleViewOptions = { type: AccessibleViewType.Help }; provideContent(): string { /* … */ } onClose(): void { /* … */ } } // In getProvider(): getProvider(accessor: ServicesAccessor) { return accessor.get(IInstantiationService).createInstance(MyFeatureAccessibilityHelpProvider); } ``` --- ## 2. Accessible View An accessible view presents the feature's visual content as plain text in a read-only editor. It is required when the feature renders rich or visual content that a screen reader cannot directly read (for example: chat responses, hover tooltips, notifications, terminal output, inline completions). If the feature is purely keyboard-driven with native text input/output (e.g., a simple input field), an accessible view is not needed — only an accessibility help dialog is required. ### Steps 1. **Create a class implementing `IAccessibleViewImplementation`** with `type = AccessibleViewType.View`. 2. **Create a content-provider** similar to the help dialog, but: - `options` — `{ type: AccessibleViewType.View }`, optionally with a `language` for syntax highlighting. - `provideContent()` — return the feature's current content as plain text. - Optionally implement `provideNextContent()` / `providePreviousContent()` for item-by-item navigation. - Implement `onClose()` to restore focus to whatever was focused before the accessible view was opened. - Optionally provide `actions` for actions the user can take from the accessible view. 3. **Register** alongside the help dialog: ```ts AccessibleViewRegistry.register(new MyFeatureAccessibleView()); ``` ### Example skeleton ```ts export class MyFeatureAccessibleView implements IAccessibleViewImplementation { readonly priority = 100; readonly name = 'my-feature'; readonly type = AccessibleViewType.View; readonly when = MyFeatureContextKeys.isFocused; getProvider(accessor: ServicesAccessor) { // Retrieve services, build content from the feature's current state const content = getMyFeatureContent(); if (!content) { return undefined; } return new AccessibleContentProvider( AccessibleViewProviderId.MyFeature, { type: AccessibleViewType.View }, () => content, () => { /* onClose — refocus whatever was focused before the accessible view opened */ }, AccessibilityVerbositySettingId.MyFeature, ); } } ``` --- ## 3. Accessibility Verbosity Setting A verbosity setting controls whether a hint such as "press Alt+F1 for accessibility help" is announced when the feature gains focus. Users who already know the shortcut can disable it. ### Steps 1. **Add an entry** to `AccessibilityVerbositySettingId` in `src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts`: ```ts export const enum AccessibilityVerbositySettingId { // … existing entries … MyFeature = 'accessibility.verbosity.myFeature' } ``` 2. **Register the configuration property** in the same file's `configuration.properties` object: ```ts [AccessibilityVerbositySettingId.MyFeature]: { description: localize('verbosity.myFeature.description', 'Provide information about how to access the My Feature accessibility help menu when My Feature is focused.'), ...baseVerbosityProperty }, ``` The `baseVerbosityProperty` gives it `type: 'boolean'`, `default: true`, and `tags: ['accessibility']`. 3. **Reference the setting key** in both the help-dialog provider (`verbositySettingKey`) and the accessible-view provider so the runtime can check whether to show the hint. --- ## 4. Accessibility Signals (Sounds & Announcements) Accessibility signals provide audible and spoken feedback for events that happen visually. Use `IAccessibilitySignalService` to play signals when something important occurs (e.g., an error appears, a task completes, content changes). ### When to use - **Use an existing signal** when the event already has one defined (see `AccessibilitySignal.*` static members — e.g., `AccessibilitySignal.error`, `AccessibilitySignal.terminalQuickFix`, `AccessibilitySignal.clear`). - **If no existing signal fits**, reach out to @meganrogge to discuss adding a new one. Do not register new signals without coordinating first. ### How signals work Each signal has two modalities controlled by user settings: - **Sound** — a short audio cue, configurable to `auto` (on when screen reader attached), `on`, or `off`. - **Announcement** — a spoken message via `aria-live`, configurable to `auto` or `off`. ### Usage ```ts // Inject the service via constructor parameter constructor( @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService ) { } // Play a signal this._accessibilitySignalService.playSignal(AccessibilitySignal.terminalQuickFix); // Play with options this._accessibilitySignalService.playSignal(AccessibilitySignal.error, { userGesture: true }); ``` --- ## 5. ARIA Alerts vs. Status Messages Use the `alert()` and `status()` functions from `src/vs/base/browser/ui/aria/aria.ts` to announce dynamic changes to screen readers. ### `alert(msg)` — Assertive live region (`role="alert"`) - **Use for**: Urgent, important information that the user must know immediately. - **Examples**: Errors, warnings, critical state changes, results of a user-initiated action. - **Behavior**: Interrupts the screen reader's current speech. ### `status(msg)` — Polite live region (`aria-live="polite"`) - **Use for**: Non-urgent, informational updates that should be spoken when the screen reader is idle. - **Examples**: Progress updates, search result counts, background state changes. - **Behavior**: Queued and spoken after the screen reader finishes its current output. ### Guidelines - **Prefer `status()` over `alert()`** unless the information is time-sensitive or the result of a direct user action. Overusing `alert()` creates a noisy, disruptive experience. - **Keep messages concise.** Screen readers read the entire message; long messages delay the user. - **Do not duplicate** — if an accessibility signal already announces the event, do not also call `alert()` / `status()` for the same information. - **Localize** all messages with `nls.localize()`. --- ## 6. Keyboard Navigation Every interactive UI element must be fully operable via the keyboard. ### Requirements - **Tab order**: All interactive elements must be reachable via `Tab` / `Shift+Tab` in a logical order. - **Arrow key navigation**: Lists, trees, grids, and toolbars must support arrow key navigation following WAI-ARIA patterns. - **Focus visibility**: Focused elements must have a visible focus indicator (VS Code's theme system provides this via `focusBorder`). - **No mouse-only interactions**: Every action reachable by click or hover must also be reachable via keyboard (context menus, buttons, toggles, etc.). - **Escape to dismiss**: Overlays, dialogs, and popups must be dismissable with `Escape`, returning focus to the previous element. - **Focus trapping**: Modal dialogs must trap focus within the dialog until dismissed. --- ## 7. ARIA Labels and Roles All interactive UI elements must have appropriate ARIA attributes so screen readers can identify and describe them. ### Requirements - **`aria-label`**: Every interactive element without visible text (icon buttons, icon-only actions, custom widgets) must have a descriptive `aria-label`. Labels should be localized. - **`aria-labelledby`** / **`aria-describedby`**: Use these to associate elements with existing visible text rather than duplicating strings. - **`role`**: Custom widgets that do not use native HTML elements must declare the correct ARIA role (e.g., `role="button"`, `role="tree"`, `role="tablist"`). - **`aria-expanded`**, **`aria-selected`**, **`aria-checked`**: Toggle and selection states must be communicated via the appropriate ARIA state attributes. - **`aria-hidden="true"`**: Decorative or redundant elements (icons next to text labels, decorative separators) must be hidden from the accessibility tree. ### Guidelines - Avoid generic labels like "button" or "icon" — describe the action: "Close panel", "Toggle sidebar", "Run task". - Test with a screen reader (VoiceOver on macOS, NVDA on Windows) to verify labels are spoken correctly in context. - Lists and trees should use `aria-setsize` and `aria-posinset` when virtualized so screen readers report the correct count. --- ## Checklist for Every New Feature - [ ] New `AccessibleViewProviderId` entry added in `accessibleView.ts` - [ ] New `AccessibilityVerbositySettingId` entry added in `accessibilityConfiguration.ts` - [ ] Verbosity setting registered in the configuration properties with `...baseVerbosityProperty` - [ ] `IAccessibleViewImplementation` with `type = Help` created and registered - [ ] Content provider references the correct `verbositySettingKey` - [ ] Help text is fully localized using `nls.localize()` - [ ] Keybindings in help text use `` syntax for dynamic resolution - [ ] `when` context key is set so the dialog only appears when the feature is focused - [ ] If the feature has rich/visual content: `IAccessibleViewImplementation` with `type = View` created and registered - [ ] Registration calls in the feature's `*.contribution.ts` file - [ ] Accessibility signal played for important events (use existing `AccessibilitySignal.*` or register a new one) - [ ] `aria.alert()` or `aria.status()` used appropriately for dynamic changes (prefer `status()` unless urgent) - [ ] All interactive elements reachable and operable via keyboard - [ ] All interactive elements without visible text have a localized `aria-label` - [ ] Custom widgets declare the correct ARIA `role` and state attributes - [ ] Decorative elements are hidden with `aria-hidden="true"` ## Key Files - `src/vs/platform/accessibility/browser/accessibleView.ts` — `AccessibleViewProviderId`, `AccessibleContentProvider`, `IAccessibleViewContentProvider` - `src/vs/platform/accessibility/browser/accessibleViewRegistry.ts` — `AccessibleViewRegistry`, `IAccessibleViewImplementation` - `src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts` — `AccessibilityVerbositySettingId`, verbosity setting registration - `src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts` — `IAccessibilitySignalService`, `AccessibilitySignal` - `src/vs/base/browser/ui/aria/aria.ts` — `alert()`, `status()` for ARIA live region announcements