[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M711CGCSF2KKD6G3PBQSH6HT":3,"skill-same:01M711CGCSF2KKD6G3PBQSH6HT":68},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":44,"commitSha":45,"skillDirectory":46,"forksCount":47,"repositoryHtmlUrl":48,"repositoryHomepageUrl":49,"ownerHtmlUrl":50,"ownerBlogUrl":51,"ownerDescription":52,"updatedAt":53,"install":54,"resources":67},"01M711CGCSF2KKD6G3PBQSH6HT","accessibility-aria-expert",null,"Detects and fixes accessibility issues in React\u002FFluent UI webviews. Use when reviewing code for screen reader compatibility, fixing ARIA labels, ensuring keyboard navigation, adding live regions for status messages, or managing focus in dialogs.","microsoft\u002Fvscode-cosmosdb","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M711CDNH6JY9J7RZVN19SNNC",200,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cosmosdb\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Faccessibility-aria-expert\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"aria","accessibility aria expert","accessibility","expert","for","fixes","compatibility","ui","live","status","screen","focus","keyboard","react","fluent","code","when","labels","issues","messages","2026-11-10 10:12:30",false,"---\nname: accessibility-aria-expert\ndescription: Detects and fixes accessibility issues in React\u002FFluent UI webviews. Use when reviewing code for screen reader compatibility, fixing ARIA labels, ensuring keyboard navigation, adding live regions for status messages, or managing focus in dialogs.\n---\n\n# Accessibility Expert for Webviews\n\nVerify and fix accessibility in React\u002FFluent UI webview components.\n\n## When to Use\n\n- Review webview code for accessibility issues\n- Fix double announcements from screen readers\n- Add missing `aria-label` to icon-only buttons or form inputs\n- Make tooltips accessible to keyboard\u002Fscreen reader users\n- Announce status changes (loading, search results, errors)\n- Manage focus when dialogs\u002Fmodals open\n- Group related controls with proper labels\n\n## Core Pattern: Tooltip Accessibility\n\nTooltips require `aria-label` + `aria-hidden` to avoid double announcements:\n\n```tsx\n\u003CTooltip content=\"Detailed explanation\">\n  \u003CBadge tabIndex={0} className=\"focusableBadge\" aria-label=\"Badge text. Detailed explanation\">\n    \u003Cspan aria-hidden=\"true\">Badge text\u003C\u002Fspan>\n  \u003C\u002FBadge>\n\u003C\u002FTooltip>\n```\n\n- `aria-label`: Full context (visible text + tooltip)\n- `aria-hidden=\"true\"`: Wraps visible text to prevent duplication\n- Screen reader hears: \"Badge text. Detailed explanation\"\n\n## Required Accessible-Name Audit\n\nFor every interactive element with visible text, compare the rendered text with its computed accessible name. Check all\nsources that can override descendant text, including `aria-label`, `aria-labelledby`, `title`, and Fluent UI tooltips with\n`relationship=\"label\"`.\n\n- The computed accessible name must contain the exact localized visible text. Additional context may follow or precede it.\n- Prefer visible descendant text as the native accessible name. For a simple flat-text description, use `aria-description` so\n  the visible label remains the name without adding hidden DOM content. Use `aria-describedby` when the description already\n  exists in the DOM, is shared by multiple controls, contains meaningful structure, or compatibility requirements demand it.\n  Add `aria-label` only when the accessible name itself needs clarification, and include the exact visible label when doing so.\n- When adding context, derive both strings from the same localized visible-label variable. Do not translate the visible and\n  accessible labels independently because translations can drift.\n- Remember that `aria-label` overrides descendant text. Seeing the visible label inside the component does not mean it is\n  included in the accessibility tree.\n- Review existing accessible-name attributes when modifying a control, even when the change does not add or alter ARIA.\n\nFor user-facing controls covered by Playwright, assert both the rendered label and computed accessible name:\n\n```tsx\nconst moreLabel = page.getByRole('button', { name: \u002F^More\u002F });\nawait expect(moreLabel).toHaveText('More…');\nawait expect(moreLabel).toHaveAccessibleName(\u002F^More…\u002F);\nawait expect(moreLabel).toHaveAccessibleDescription('Show full description');\n```\n\n## Avoid Hidden-Text Scroll Regressions\n\nDo not add an absolutely positioned screen-reader-only element just to name a nearby semantic element, especially inside a\nscrollable table, grid, list, or panel. VoiceOver may move the virtual cursor to the hidden element's layout box and scroll the\nvisible content out of view.\n\nPrefer setting the accessible name or description on a rendered element with useful on-screen bounds. For text controls, this is\nusually the semantic element itself. For an icon or symbol inside a table cell, use a visible inline wrapper as the named\naccessibility node and keep the visual-only child hidden from assistive technology:\n\n```tsx\n\u002F\u002F ❌ VoiceOver may scroll to the hidden span when navigating the cell.\n\u003Ctd>\n  \u003Cspan className={styles.srOnly}>{l10n.t('Yes')}\u003C\u002Fspan>\n  \u003CCheckmarkCircleFilled \u002F>\n\u003C\u002Ftd>\n\n\u002F\u002F ❌ The value is announced, but VoiceOver's visual focus indicator may disappear from the rendered symbol.\n\u003Ctd aria-label={l10n.t('Yes')}>\n  \u003CCheckmarkCircleFilled aria-hidden={true} \u002F>\n\u003C\u002Ftd>\n\n\u002F\u002F ✅ The accessibility node uses the visible symbol's bounds; there is no off-screen layout box to reveal.\n\u003Ctd>\n  \u003Cspan role=\"img\" aria-label={l10n.t('Yes')} style={{ display: 'inline-flex' }}>\n    \u003CCheckmarkCircleFilled aria-hidden={true} \u002F>\n  \u003C\u002Fspan>\n\u003C\u002Ftd>\n```\n\nUse hidden DOM text only when the semantic relationship cannot be expressed directly. When it is necessary, use an established\nshared utility, anchor it to an appropriate containing block, and manually navigate the control with VoiceOver or NVDA to verify\nthat focus and the virtual cursor do not scroll the visible content away or lose the visual focus indicator. Playwright\naccessible-name assertions do not detect these screen-reader-driven visual regressions.\n\n## Detection Rules\n\n### 1. Tooltip Without aria-label Context\n\n❌ **Problem**: Tooltip content inaccessible to screen readers\n\n```tsx\n\u003CTooltip content=\"Save document to database\">\n  \u003CButton aria-label=\"Save\">Save\u003C\u002FButton>\n\u003C\u002FTooltip>\n```\n\n✅ **Fix**: Include tooltip in aria-label\n\n```tsx\n\u003CTooltip content=\"Save document to database\" relationship=\"description\">\n  \u003CButton aria-label=\"Save document to database\">Save\u003C\u002FButton>\n\u003C\u002FTooltip>\n```\n\n### 2. Missing aria-hidden (Double Announcement)\n\n❌ **Problem**: Screen reader says \"Collection scan Collection scan\"\n\n```tsx\n\u003CBadge aria-label=\"Collection scan. Query is inefficient\">Collection scan\u003C\u002FBadge>\n```\n\n✅ **Fix**: Wrap visible text\n\n```tsx\n\u003CBadge aria-label=\"Collection scan. Query is inefficient\">\n  \u003Cspan aria-hidden=\"true\">Collection scan\u003C\u002Fspan>\n\u003C\u002FBadge>\n```\n\n### 3. Redundant aria-label (NOT Needed)\n\n❌ **Problem**: aria-label identical to visible text adds no value\n\n```tsx\n\u003CButton aria-label=\"Save\">Save\u003C\u002FButton>\n\u003CToolbarButton aria-label=\"Validate\" icon={\u003CCheckIcon \u002F>}>Validate\u003C\u002FToolbarButton>\n```\n\n✅ **Fix**: Remove redundant aria-label OR make it more descriptive\n\n```tsx\n\u003CButton>Save\u003C\u002FButton>\n\u003CToolbarButton icon={\u003CCheckIcon \u002F>}>Validate\u003C\u002FToolbarButton>\n```\n\n**Keep aria-label only when it adds information:**\n\n```tsx\n\u003CToolbarButton aria-label=\"Save document to database\" icon={\u003CSaveIcon \u002F>}>\n  Save\n\u003C\u002FToolbarButton>\n```\n\n### 4. Icon-Only Button Missing aria-label\n\n❌ **Problem**: No accessible name\n\n```tsx\n\u003CToolbarButton icon={\u003CDeleteRegular \u002F>} onClick={onDelete} \u002F>\n```\n\n✅ **Fix**: Add aria-label\n\n```tsx\n\u003CTooltip content=\"Delete selected items\" relationship=\"description\">\n  \u003CToolbarButton aria-label=\"Delete selected items\" icon={\u003CDeleteRegular \u002F>} onClick={onDelete} \u002F>\n\u003C\u002FTooltip>\n```\n\n### 5. Decorative Elements Not Hidden\n\n❌ **Problem**: Progress bar announced unnecessarily\n\n```tsx\n\u003CProgressBar thickness=\"large\" \u002F>\n```\n\n✅ **Fix**: Hide decorative elements\n\n```tsx\n\u003CProgressBar thickness=\"large\" aria-hidden={true} \u002F>\n```\n\n### 6. Input Missing Accessible Name\n\n❌ **Problem**: SpinButton\u002FInput without accessible name\n\n```tsx\n\u003CSpinButton value={skipValue} onChange={onSkipChange} \u002F>\n\u003CInput placeholder=\"Enter query...\" \u002F>\n```\n\n✅ **Fix**: Add aria-label or associate with label element\n\n```tsx\n\u003CSpinButton aria-label=\"Skip documents\" value={skipValue} onChange={onSkipChange} \u002F>\n\u003CLabel htmlFor=\"query-input\">Query\u003C\u002FLabel>\n\u003CInput id=\"query-input\" placeholder=\"Enter query...\" \u002F>\n```\n\n### 7. Visible Label Not in Accessible Name\n\n❌ **Problem**: aria-label doesn't contain visible text (breaks voice control)\n\n```tsx\n\u003CToolbarButton aria-label=\"Reload data\" icon={\u003CRefreshIcon \u002F>}>\n  Refresh\n\u003C\u002FToolbarButton>\n```\n\n✅ **Fix**: Accessible name must contain visible label exactly\n\n```tsx\n\u003CToolbarButton aria-label=\"Refresh data\" icon={\u003CRefreshIcon \u002F>}>\n  Refresh\n\u003C\u002FToolbarButton>\n```\n\nVoice control users say \"click Refresh\" – only works if accessible name contains \"Refresh\".\n\n### 8. Status Changes Not Announced\n\n❌ **Problem**: Screen reader doesn't announce dynamic content\n\n```tsx\n\u003Cspan>{isLoading ? 'Loading...' : `${count} results`}\u003C\u002Fspan>\n```\n\n✅ **Fix**: Use the `Announcer` component\n\n```tsx\nimport { Announcer } from '..\u002F..\u002Fapi\u002Fwebview-client\u002Faccessibility';\n\n\u002F\u002F Announces when `when` transitions from false to true\n\u003CAnnouncer when={isLoading} message={l10n.t('Loading...')} \u002F>\n\n\u002F\u002F Dynamic message based on state\n\u003CAnnouncer\n    when={!isLoading && documentCount !== undefined}\n    message={documentCount > 0 ? l10n.t('Results found') : l10n.t('No results found')}\n\u002F>\n```\n\nUse for: loading states, search results, success\u002Ferror messages.\n\n### 9. Dialog Opens Without Focus Move\n\n❌ **Problem**: Focus stays on trigger when modal opens\n\n```tsx\n{\n  isOpen && \u003CDialog>...\u003C\u002FDialog>;\n}\n```\n\n✅ **Fix**: Move focus programmatically\n\n```tsx\nconst dialogRef = useRef\u003CHTMLDivElement>(null);\n\nuseEffect(() => {\n  if (isOpen) dialogRef.current?.focus();\n}, [isOpen]);\n\n{\n  isOpen && (\n    \u003CDialog ref={dialogRef} tabIndex={-1} aria-modal=\"true\">\n      ...\n    \u003C\u002FDialog>\n  );\n}\n```\n\n### 10. Related Controls Without Group Label\n\n❌ **Problem**: Buttons share visual label but screen reader misses context\n\n```tsx\n\u003Cspan>How would you rate this?\u003C\u002Fspan>\n\u003CButton>👍\u003C\u002FButton>\n\u003CButton>👎\u003C\u002FButton>\n```\n\n✅ **Fix**: Use role=\"group\" with aria-labelledby\n\n```tsx\n\u003Cdiv role=\"group\" aria-labelledby=\"rating-label\">\n  \u003Cspan id=\"rating-label\">How would you rate this?\u003C\u002Fspan>\n  \u003CButton aria-label=\"I like it\">👍\u003C\u002FButton>\n  \u003CButton aria-label=\"I don't like it\">👎\u003C\u002FButton>\n\u003C\u002Fdiv>\n```\n\n## When to Use aria-hidden\n\n**DO use** on:\n\n- Visible text when aria-label provides complete context\n- Decorative icons, spinners, progress bars\n- Visual separators (\\`|\\`, \\`—\\`)\n\n**DO NOT use** on:\n\n- The only accessible content (hides it completely)\n- Interactive\u002Ffocusable elements\n- Error messages or alerts\n\n## focusableBadge Pattern\n\nFor keyboard-accessible badges with tooltips:\n\n1. Import: \\`import '..\u002Fcomponents\u002FfocusableBadge\u002FfocusableBadge.scss';\\`\n2. Apply attributes:\n\n```tsx\n\u003CBadge tabIndex={0} className=\"focusableBadge\" aria-label=\"Visible text. Tooltip details\">\n  \u003Cspan aria-hidden=\"true\">Visible text\u003C\u002Fspan>\n\u003C\u002FBadge>\n```\n\n## Screen Reader Announcements\n\nUse the `Announcer` component for WCAG 4.1.3 (Status Messages) compliance.\n\n```tsx\nimport { Announcer } from '..\u002F..\u002Fapi\u002Fwebview-client\u002Faccessibility';\n```\n\n### Basic Usage\n\n```tsx\n\u002F\u002F Announces \"AI is analyzing...\" when isLoading becomes true\n\u003CAnnouncer when={isLoading} message={l10n.t('AI is analyzing...')} \u002F>\n\n\u002F\u002F Dynamic message based on state (e.g., query results)\n\u003CAnnouncer\n    when={!isLoading && documentCount !== undefined}\n    message={documentCount > 0 ? l10n.t('Results found') : l10n.t('No results found')}\n\u002F>\n\n\u002F\u002F With assertive politeness (default is polite)\n\u003CAnnouncer when={hasError} message={l10n.t('Error occurred')} politeness=\"assertive\" \u002F>\n```\n\n### Props\n\n- `when`: Announces when this transitions from `false` to `true`\n- `message`: The message to announce (use `l10n.t()` for localization)\n- `politeness`: `'assertive'` (default, interrupts) or `'polite'` (waits for idle)\n\n### Key Points\n\n- **Placement doesn't matter** - screen readers monitor all live regions regardless of DOM position; place near related UI for code readability\n- **Store relevant state** (e.g., `documentCount`) to derive dynamic messages\n- **Use `l10n.t()` for messages** - announcements must be localized\n- **Condition resets automatically** - when `when` goes back to `false`, it's ready for the next announcement\n- **Prefer 'assertive'** for user-initiated actions, 'polite' for background updates\n\n## Quick Checklist\n\n- [ ] Icon-only buttons have `aria-label`\n- [ ] Form inputs have associated labels or `aria-label`\n- [ ] Tooltip content included in `aria-label`\n- [ ] Visible text wrapped in `aria-hidden=\"true\"` when aria-label duplicates it\n- [ ] Redundant aria-labels removed (identical to visible text)\n- [ ] Accessible names contain the exact localized visible label (for voice control)\n- [ ] VoiceOver\u002FNVDA navigation neither scrolls visible content away nor loses its visual focus indicator\n- [ ] Decorative elements have `aria-hidden={true}`\n- [ ] Badges with tooltips use `focusableBadge` class + `tabIndex={0}`\n- [ ] Status updates use `Announcer` component\n- [ ] Focus moves to dialog\u002Fmodal content when opened\n- [ ] Related controls wrapped in `role=\"group\"` with `aria-labelledby`\n\n## References\n\n- [WCAG 2.1.1 Keyboard](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Fkeyboard.html)\n- [WCAG 2.4.3 Focus Order](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Ffocus-order.html)\n- [WCAG 2.5.3 Label in Name](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Flabel-in-name.html)\n- [WCAG 4.1.2 Name, Role, Value](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Fname-role-value.html)\n- [WCAG 4.1.3 Status Messages](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Fstatus-messages.html)\n- See `src\u002Fwebviews\u002Fcomponents\u002FfocusableBadge\u002FfocusableBadge.md` for the Badge pattern\n","# Accessibility Expert for Webviews\n\nVerify and fix accessibility in React\u002FFluent UI webview components.\n\n## When to Use\n\n- Review webview code for accessibility issues\n- Fix double announcements from screen readers\n- Add missing `aria-label` to icon-only buttons or form inputs\n- Make tooltips accessible to keyboard\u002Fscreen reader users\n- Announce status changes (loading, search results, errors)\n- Manage focus when dialogs\u002Fmodals open\n- Group related controls with proper labels\n\n## Core Pattern: Tooltip Accessibility\n\nTooltips require `aria-label` + `aria-hidden` to avoid double announcements:\n\n```tsx\n\u003CTooltip content=\"Detailed explanation\">\n  \u003CBadge tabIndex={0} className=\"focusableBadge\" aria-label=\"Badge text. Detailed explanation\">\n    \u003Cspan aria-hidden=\"true\">Badge text\u003C\u002Fspan>\n  \u003C\u002FBadge>\n\u003C\u002FTooltip>\n```\n\n- `aria-label`: Full context (visible text + tooltip)\n- `aria-hidden=\"true\"`: Wraps visible text to prevent duplication\n- Screen reader hears: \"Badge text. Detailed explanation\"\n\n## Required Accessible-Name Audit\n\nFor every interactive element with visible text, compare the rendered text with its computed accessible name. Check all\nsources that can override descendant text, including `aria-label`, `aria-labelledby`, `title`, and Fluent UI tooltips with\n`relationship=\"label\"`.\n\n- The computed accessible name must contain the exact localized visible text. Additional context may follow or precede it.\n- Prefer visible descendant text as the native accessible name. For a simple flat-text description, use `aria-description` so\n  the visible label remains the name without adding hidden DOM content. Use `aria-describedby` when the description already\n  exists in the DOM, is shared by multiple controls, contains meaningful structure, or compatibility requirements demand it.\n  Add `aria-label` only when the accessible name itself needs clarification, and include the exact visible label when doing so.\n- When adding context, derive both strings from the same localized visible-label variable. Do not translate the visible and\n  accessible labels independently because translations can drift.\n- Remember that `aria-label` overrides descendant text. Seeing the visible label inside the component does not mean it is\n  included in the accessibility tree.\n- Review existing accessible-name attributes when modifying a control, even when the change does not add or alter ARIA.\n\nFor user-facing controls covered by Playwright, assert both the rendered label and computed accessible name:\n\n```tsx\nconst moreLabel = page.getByRole('button', { name: \u002F^More\u002F });\nawait expect(moreLabel).toHaveText('More…');\nawait expect(moreLabel).toHaveAccessibleName(\u002F^More…\u002F);\nawait expect(moreLabel).toHaveAccessibleDescription('Show full description');\n```\n\n## Avoid Hidden-Text Scroll Regressions\n\nDo not add an absolutely positioned screen-reader-only element just to name a nearby semantic element, especially inside a\nscrollable table, grid, list, or panel. VoiceOver may move the virtual cursor to the hidden element's layout box and scroll the\nvisible content out of view.\n\nPrefer setting the accessible name or description on a rendered element with useful on-screen bounds. For text controls, this is\nusually the semantic element itself. For an icon or symbol inside a table cell, use a visible inline wrapper as the named\naccessibility node and keep the visual-only child hidden from assistive technology:\n\n```tsx\n\u002F\u002F ❌ VoiceOver may scroll to the hidden span when navigating the cell.\n\u003Ctd>\n  \u003Cspan className={styles.srOnly}>{l10n.t('Yes')}\u003C\u002Fspan>\n  \u003CCheckmarkCircleFilled \u002F>\n\u003C\u002Ftd>\n\n\u002F\u002F ❌ The value is announced, but VoiceOver's visual focus indicator may disappear from the rendered symbol.\n\u003Ctd aria-label={l10n.t('Yes')}>\n  \u003CCheckmarkCircleFilled aria-hidden={true} \u002F>\n\u003C\u002Ftd>\n\n\u002F\u002F ✅ The accessibility node uses the visible symbol's bounds; there is no off-screen layout box to reveal.\n\u003Ctd>\n  \u003Cspan role=\"img\" aria-label={l10n.t('Yes')} style={{ display: 'inline-flex' }}>\n    \u003CCheckmarkCircleFilled aria-hidden={true} \u002F>\n  \u003C\u002Fspan>\n\u003C\u002Ftd>\n```\n\nUse hidden DOM text only when the semantic relationship cannot be expressed directly. When it is necessary, use an established\nshared utility, anchor it to an appropriate containing block, and manually navigate the control with VoiceOver or NVDA to verify\nthat focus and the virtual cursor do not scroll the visible content away or lose the visual focus indicator. Playwright\naccessible-name assertions do not detect these screen-reader-driven visual regressions.\n\n## Detection Rules\n\n### 1. Tooltip Without aria-label Context\n\n❌ **Problem**: Tooltip content inaccessible to screen readers\n\n```tsx\n\u003CTooltip content=\"Save document to database\">\n  \u003CButton aria-label=\"Save\">Save\u003C\u002FButton>\n\u003C\u002FTooltip>\n```\n\n✅ **Fix**: Include tooltip in aria-label\n\n```tsx\n\u003CTooltip content=\"Save document to database\" relationship=\"description\">\n  \u003CButton aria-label=\"Save document to database\">Save\u003C\u002FButton>\n\u003C\u002FTooltip>\n```\n\n### 2. Missing aria-hidden (Double Announcement)\n\n❌ **Problem**: Screen reader says \"Collection scan Collection scan\"\n\n```tsx\n\u003CBadge aria-label=\"Collection scan. Query is inefficient\">Collection scan\u003C\u002FBadge>\n```\n\n✅ **Fix**: Wrap visible text\n\n```tsx\n\u003CBadge aria-label=\"Collection scan. Query is inefficient\">\n  \u003Cspan aria-hidden=\"true\">Collection scan\u003C\u002Fspan>\n\u003C\u002FBadge>\n```\n\n### 3. Redundant aria-label (NOT Needed)\n\n❌ **Problem**: aria-label identical to visible text adds no value\n\n```tsx\n\u003CButton aria-label=\"Save\">Save\u003C\u002FButton>\n\u003CToolbarButton aria-label=\"Validate\" icon={\u003CCheckIcon \u002F>}>Validate\u003C\u002FToolbarButton>\n```\n\n✅ **Fix**: Remove redundant aria-label OR make it more descriptive\n\n```tsx\n\u003CButton>Save\u003C\u002FButton>\n\u003CToolbarButton icon={\u003CCheckIcon \u002F>}>Validate\u003C\u002FToolbarButton>\n```\n\n**Keep aria-label only when it adds information:**\n\n```tsx\n\u003CToolbarButton aria-label=\"Save document to database\" icon={\u003CSaveIcon \u002F>}>\n  Save\n\u003C\u002FToolbarButton>\n```\n\n### 4. Icon-Only Button Missing aria-label\n\n❌ **Problem**: No accessible name\n\n```tsx\n\u003CToolbarButton icon={\u003CDeleteRegular \u002F>} onClick={onDelete} \u002F>\n```\n\n✅ **Fix**: Add aria-label\n\n```tsx\n\u003CTooltip content=\"Delete selected items\" relationship=\"description\">\n  \u003CToolbarButton aria-label=\"Delete selected items\" icon={\u003CDeleteRegular \u002F>} onClick={onDelete} \u002F>\n\u003C\u002FTooltip>\n```\n\n### 5. Decorative Elements Not Hidden\n\n❌ **Problem**: Progress bar announced unnecessarily\n\n```tsx\n\u003CProgressBar thickness=\"large\" \u002F>\n```\n\n✅ **Fix**: Hide decorative elements\n\n```tsx\n\u003CProgressBar thickness=\"large\" aria-hidden={true} \u002F>\n```\n\n### 6. Input Missing Accessible Name\n\n❌ **Problem**: SpinButton\u002FInput without accessible name\n\n```tsx\n\u003CSpinButton value={skipValue} onChange={onSkipChange} \u002F>\n\u003CInput placeholder=\"Enter query...\" \u002F>\n```\n\n✅ **Fix**: Add aria-label or associate with label element\n\n```tsx\n\u003CSpinButton aria-label=\"Skip documents\" value={skipValue} onChange={onSkipChange} \u002F>\n\u003CLabel htmlFor=\"query-input\">Query\u003C\u002FLabel>\n\u003CInput id=\"query-input\" placeholder=\"Enter query...\" \u002F>\n```\n\n### 7. Visible Label Not in Accessible Name\n\n❌ **Problem**: aria-label doesn't contain visible text (breaks voice control)\n\n```tsx\n\u003CToolbarButton aria-label=\"Reload data\" icon={\u003CRefreshIcon \u002F>}>\n  Refresh\n\u003C\u002FToolbarButton>\n```\n\n✅ **Fix**: Accessible name must contain visible label exactly\n\n```tsx\n\u003CToolbarButton aria-label=\"Refresh data\" icon={\u003CRefreshIcon \u002F>}>\n  Refresh\n\u003C\u002FToolbarButton>\n```\n\nVoice control users say \"click Refresh\" – only works if accessible name contains \"Refresh\".\n\n### 8. Status Changes Not Announced\n\n❌ **Problem**: Screen reader doesn't announce dynamic content\n\n```tsx\n\u003Cspan>{isLoading ? 'Loading...' : `${count} results`}\u003C\u002Fspan>\n```\n\n✅ **Fix**: Use the `Announcer` component\n\n```tsx\nimport { Announcer } from '..\u002F..\u002Fapi\u002Fwebview-client\u002Faccessibility';\n\n\u002F\u002F Announces when `when` transitions from false to true\n\u003CAnnouncer when={isLoading} message={l10n.t('Loading...')} \u002F>\n\n\u002F\u002F Dynamic message based on state\n\u003CAnnouncer\n    when={!isLoading && documentCount !== undefined}\n    message={documentCount > 0 ? l10n.t('Results found') : l10n.t('No results found')}\n\u002F>\n```\n\nUse for: loading states, search results, success\u002Ferror messages.\n\n### 9. Dialog Opens Without Focus Move\n\n❌ **Problem**: Focus stays on trigger when modal opens\n\n```tsx\n{\n  isOpen && \u003CDialog>...\u003C\u002FDialog>;\n}\n```\n\n✅ **Fix**: Move focus programmatically\n\n```tsx\nconst dialogRef = useRef\u003CHTMLDivElement>(null);\n\nuseEffect(() => {\n  if (isOpen) dialogRef.current?.focus();\n}, [isOpen]);\n\n{\n  isOpen && (\n    \u003CDialog ref={dialogRef} tabIndex={-1} aria-modal=\"true\">\n      ...\n    \u003C\u002FDialog>\n  );\n}\n```\n\n### 10. Related Controls Without Group Label\n\n❌ **Problem**: Buttons share visual label but screen reader misses context\n\n```tsx\n\u003Cspan>How would you rate this?\u003C\u002Fspan>\n\u003CButton>👍\u003C\u002FButton>\n\u003CButton>👎\u003C\u002FButton>\n```\n\n✅ **Fix**: Use role=\"group\" with aria-labelledby\n\n```tsx\n\u003Cdiv role=\"group\" aria-labelledby=\"rating-label\">\n  \u003Cspan id=\"rating-label\">How would you rate this?\u003C\u002Fspan>\n  \u003CButton aria-label=\"I like it\">👍\u003C\u002FButton>\n  \u003CButton aria-label=\"I don't like it\">👎\u003C\u002FButton>\n\u003C\u002Fdiv>\n```\n\n## When to Use aria-hidden\n\n**DO use** on:\n\n- Visible text when aria-label provides complete context\n- Decorative icons, spinners, progress bars\n- Visual separators (\\`|\\`, \\`—\\`)\n\n**DO NOT use** on:\n\n- The only accessible content (hides it completely)\n- Interactive\u002Ffocusable elements\n- Error messages or alerts\n\n## focusableBadge Pattern\n\nFor keyboard-accessible badges with tooltips:\n\n1. Import: \\`import '..\u002Fcomponents\u002FfocusableBadge\u002FfocusableBadge.scss';\\`\n2. Apply attributes:\n\n```tsx\n\u003CBadge tabIndex={0} className=\"focusableBadge\" aria-label=\"Visible text. Tooltip details\">\n  \u003Cspan aria-hidden=\"true\">Visible text\u003C\u002Fspan>\n\u003C\u002FBadge>\n```\n\n## Screen Reader Announcements\n\nUse the `Announcer` component for WCAG 4.1.3 (Status Messages) compliance.\n\n```tsx\nimport { Announcer } from '..\u002F..\u002Fapi\u002Fwebview-client\u002Faccessibility';\n```\n\n### Basic Usage\n\n```tsx\n\u002F\u002F Announces \"AI is analyzing...\" when isLoading becomes true\n\u003CAnnouncer when={isLoading} message={l10n.t('AI is analyzing...')} \u002F>\n\n\u002F\u002F Dynamic message based on state (e.g., query results)\n\u003CAnnouncer\n    when={!isLoading && documentCount !== undefined}\n    message={documentCount > 0 ? l10n.t('Results found') : l10n.t('No results found')}\n\u002F>\n\n\u002F\u002F With assertive politeness (default is polite)\n\u003CAnnouncer when={hasError} message={l10n.t('Error occurred')} politeness=\"assertive\" \u002F>\n```\n\n### Props\n\n- `when`: Announces when this transitions from `false` to `true`\n- `message`: The message to announce (use `l10n.t()` for localization)\n- `politeness`: `'assertive'` (default, interrupts) or `'polite'` (waits for idle)\n\n### Key Points\n\n- **Placement doesn't matter** - screen readers monitor all live regions regardless of DOM position; place near related UI for code readability\n- **Store relevant state** (e.g., `documentCount`) to derive dynamic messages\n- **Use `l10n.t()` for messages** - announcements must be localized\n- **Condition resets automatically** - when `when` goes back to `false`, it's ready for the next announcement\n- **Prefer 'assertive'** for user-initiated actions, 'polite' for background updates\n\n## Quick Checklist\n\n- [ ] Icon-only buttons have `aria-label`\n- [ ] Form inputs have associated labels or `aria-label`\n- [ ] Tooltip content included in `aria-label`\n- [ ] Visible text wrapped in `aria-hidden=\"true\"` when aria-label duplicates it\n- [ ] Redundant aria-labels removed (identical to visible text)\n- [ ] Accessible names contain the exact localized visible label (for voice control)\n- [ ] VoiceOver\u002FNVDA navigation neither scrolls visible content away nor loses its visual focus indicator\n- [ ] Decorative elements have `aria-hidden={true}`\n- [ ] Badges with tooltips use `focusableBadge` class + `tabIndex={0}`\n- [ ] Status updates use `Announcer` component\n- [ ] Focus moves to dialog\u002Fmodal content when opened\n- [ ] Related controls wrapped in `role=\"group\"` with `aria-labelledby`\n\n## References\n\n- [WCAG 2.1.1 Keyboard](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Fkeyboard.html)\n- [WCAG 2.4.3 Focus Order](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Ffocus-order.html)\n- [WCAG 2.5.3 Label in Name](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Flabel-in-name.html)\n- [WCAG 4.1.2 Name, Role, Value](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Fname-role-value.html)\n- [WCAG 4.1.3 Status Messages](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Fstatus-messages.html)\n- See `src\u002Fwebviews\u002Fcomponents\u002FfocusableBadge\u002FfocusableBadge.md` for the Badge pattern\n",{"name":5,"description":7},"main","4d0ccf8e835f0cbf4788732ff7a42930e4b7c6d8",".github\u002Fskills\u002Faccessibility-aria-expert",89,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cosmosdb","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-azuretools.vscode-cosmosdb","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-11 02:14:56",{"source":8,"skillName":5,"commands":55,"prompt":65,"zipAvailable":66},[56,59,62],{"tool":57,"command":58},"npx","npx skills add microsoft\u002Fvscode-cosmosdb --skill accessibility-aria-expert",{"tool":60,"command":61},"bunx","bunx skills add microsoft\u002Fvscode-cosmosdb --skill accessibility-aria-expert",{"tool":63,"command":64},"pnpm","pnpm dlx skills add microsoft\u002Fvscode-cosmosdb --skill accessibility-aria-expert","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cosmosdb\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Faccessibility-aria-expert\u002FSKILL.md",true,[],[69,99,126,145,171,203],{"skillNo":70,"name":71,"nameZh":6,"description":72,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":80,"publishedAt":39},"01M711DAKP4A0MGZ9GB677588E","cosmosdb-relational-migration","Orchestrate relational database migrations to Azure Cosmos DB for NoSQL from source\ndiscovery through schema conversion, provisioning, and application code migration.\nUse when asked to migrate an RDBMS application, run or resume a\n.cosmosdb-migration project, infer relational DDL from code or other artifacts when\nno DDL is available, verify whether the application language has a supported Cosmos DB\nSDK, execute one migration phase, or remember migration findings, decisions, or a\ngenerated Cosmos DB model for another application, or review migration blockers\nand propose fixes for approval. Requires the\ncosmosdb-best-practices peer Skill.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cosmosdb\u002Fblob\u002Fmain\u002Fskills\u002Fcosmosdb-relational-migration\u002FSKILL.md",[75],{"categoryNo":76,"categoryName":77,"taxonomyType":78,"taxonomyTypeDesc":79},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[81,82,83,84,85,86,87,88,89,90,91,35,92,34,93,94,95,96,97,98],"cosmosdb relational migration","relational","migration","orchestrate","code migration","infer","model","db","project","generated","database","migrate","conversion","phase","review","run","database migrations","nosql",{"skillNo":100,"name":101,"nameZh":6,"description":102,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":108,"publishedAt":39},"01M711D5WVTMXJ79CEWR7YWNW2","cosmosdb-nosql-query-generation","Generate, explain, edit, and fix Azure Cosmos DB for NoSQL (SQL API) queries. Use\nwhenever you need to produce a syntactically correct, safe Cosmos DB NoSQL query — for\nexample when the user asks to generate, write, edit, fix, or explain a Cosmos DB NoSQL\nquery. Provides the NoSQL dialect rules, safety rules, and few-shot examples. Covers\nSELECT\u002FVALUE\u002FDISTINCT\u002FTOP, array-unwind JOINs, subqueries, WHERE\u002FBETWEEN\u002FIN\u002FLIKE,\nGROUP BY and aggregates, ORDER BY and ORDER BY RANK, OFFSET\u002FLIMIT, the full built-in\nfunction reference, and how Cosmos DB NoSQL differs from T-SQL \u002F PostgreSQL \u002F MySQL.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cosmosdb\u002Fblob\u002Fmain\u002Fskills\u002Fcosmosdb-nosql-query-generation\u002FSKILL.md",[105],{"categoryNo":106,"categoryName":107,"taxonomyType":78,"taxonomyTypeDesc":79},"01JBSEEDCAT000000000000010","后端与接口",[109,98,110,111,88,112,113,114,35,115,116,117,118,119,120,121,122,123,124,125],"query","cosmosdb nosql query generation","generation","select","aggregates","fix","you","api","mysql","postgresql","top","generate","how","sql","write","safe","edit",{"skillNo":127,"name":128,"nameZh":6,"description":129,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":130,"categories":131,"keywords":132,"publishedAt":39},"01M711D2B9HC4SG6TQ22F0DVM4","cosmosdb-nosql-query-editor","Drive the active Azure Cosmos DB for NoSQL Query Editor in VS Code from natural\nlanguage. Use whenever the user asks in natural language to show \u002F find \u002F list \u002F count \u002F\nfilter data \"in this container\", \"in my container\", or in the active Cosmos DB Query\nEditor (for example: \"show me all trucks in this container\", \"find active users\",\n\"count documents by type\"), or to generate, edit, or explain a query for the active\neditor. This skill orchestrates the VS Code Language Model tools that read editor\ncontext, sample the container schema, apply a query, and run it; it delegates all\nCosmos DB NoSQL query-language rules, syntax, functions, and examples to the\ncosmosdb-nosql-query-generation skill.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cosmosdb\u002Fblob\u002Fmain\u002Fskills\u002Fcosmosdb-nosql-query-editor\u002FSKILL.md",[],[109,98,133,134,135,87,88,136,137,34,138,139,140,141,120,142,96,143,125,144],"cosmosdb nosql query editor","editor","show","documents","show me","filter","db query","sample","me","active","read","syntax",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":149,"categories":150,"keywords":156,"publishedAt":39},"01M711CZ4AKAN8XX4EJSTZSW1T","cosmosdb-data-model-recommendation","Recommend Azure Cosmos DB for NoSQL partition keys from container schemas, query\npatterns, write rates, cardinality, and growth estimates. Use for Data Modeler\nrecommendations or direct Chat requests to choose, compare, score, or explain\npartition keys, including hierarchical and synthetic keys. Reuses the\ncosmosdb-best-practices skill and prefers suitable scenario hints for unchanged defaults.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cosmosdb\u002Fblob\u002Fmain\u002Fskills\u002Fcosmosdb-data-model-recommendation\u002FSKILL.md",[151],{"categoryNo":152,"categoryName":153,"taxonomyType":154,"taxonomyTypeDesc":155},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[87,157,158,159,160,161,88,162,163,109,164,165,123,166,167,98,168,169,23,170],"data","data model","recommendation","model recommendation","cosmosdb data model recommendation","partition","choose","synthetic","recommendations","recommend","scenario","direct","patterns","cosmos",{"skillNo":172,"name":173,"nameZh":6,"description":174,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":175,"categories":176,"keywords":185,"publishedAt":39},"01M711CSNGXF95EMZGJM4CQMMW","cosmosdb-best-practices","Azure Cosmos DB performance optimization and best practices guidelines for NoSQL,\npartitioning, queries, and SDK usage. Use when writing, reviewing, or refactoring\ncode that interacts with Azure Cosmos DB, designing data models, optimizing queries,\nor implementing high-performance database operations.\nUSE FOR: Cosmos DB NoSQL, partition key design, RU optimization, point reads,\ncross-partition queries, SDK singleton, CosmosClient, container modeling,\nchange feed, bulk operations, vector search, full-text search, hierarchical\npartition keys, global distribution, autoscale throughput, indexing policy,\nDO NOT USE FOR: PostgreSQL, MySQL, MongoDB (non-Azure), DynamoDB, Cassandra,\nAzure SQL, Cosmos DB for PostgreSQL (vCore), Cosmos DB for MongoDB vCore, Azure DocumentDB,\ngeneral SQL databases, Redis, Elasticsearch.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cosmosdb\u002Fblob\u002Fmain\u002Fskills\u002Fcosmosdb-best-practices\u002FSKILL.md",[177,178,181,184],{"categoryNo":106,"categoryName":107,"taxonomyType":78,"taxonomyTypeDesc":79},{"categoryNo":179,"categoryName":180,"taxonomyType":78,"taxonomyTypeDesc":79},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":182,"categoryName":183,"taxonomyType":154,"taxonomyTypeDesc":155},"01JBSEEDCAT000000000000030","开发工程师",{"categoryNo":152,"categoryName":153,"taxonomyType":154,"taxonomyTypeDesc":155},[186,187,188,189,190,88,191,162,192,193,194,195,196,197,198,199,200,201,202,91],"best practices","cosmosdb best practices","bulk","databases","distribution","performance","search","vector","implementing","database operations","design","kit","mongodb","writing","elasticsearch","singleton","high-performance",{"skillNo":204,"name":205,"nameZh":6,"description":206,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":207,"categories":208,"keywords":209,"publishedAt":39},"01M711CPFKVFSTFRXR0S5T4S59","telemetry-best-practices","Reviews and authors telemetry code in this extension. Use when adding, modifying, or reviewing any `callWithTelemetryAndErrorHandling` call, any `context.telemetry.properties`\u002F`measurements` assignment, or any helper that reports stats to telemetry. Ensures no PII\u002FEUII is emitted, that property vs. measurement usage is correct, and that event\u002Fproperty names are consistent.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cosmosdb\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftelemetry-best-practices\u002FSKILL.md",[],[210,186,211,212,213,35,34,214,215,216,217,218,219,220,221,222,223,224,225],"telemetry","telemetry best practices","reviews","pii","reports","context","measurement","usage","event","correct","call","use","helper","stats","properties","extension"]