accessibility-aria-expert

作者: microsoft

检测并修复React/Fluent UI网页视图中的无障碍问题。在审查代码以确保屏幕阅读器兼容性、修复ARIA标签、确保…时使用。

npx skills add https://github.com/microsoft/vscode-cosmosdb --skill accessibility-aria-expert

Accessibility Expert for Webviews

Verify and fix accessibility in React/Fluent UI webview components.

When to Use

  • Review webview code for accessibility issues
  • Fix double announcements from screen readers
  • Add missing aria-label to icon-only buttons or form inputs
  • Make tooltips accessible to keyboard/screen reader users
  • Announce status changes (loading, search results, errors)
  • Manage focus when dialogs/modals open
  • Group related controls with proper labels

Core Pattern: Tooltip Accessibility

Tooltips require aria-label + aria-hidden to avoid double announcements:

<Tooltip content="Detailed explanation">
  <Badge tabIndex={0} className="focusableBadge" aria-label="Badge text. Detailed explanation">
    <span aria-hidden="true">Badge text</span>
  </Badge>
</Tooltip>
  • aria-label: Full context (visible text + tooltip)
  • aria-hidden="true": Wraps visible text to prevent duplication
  • Screen reader hears: "Badge text. Detailed explanation"

Required Accessible-Name Audit

For every interactive element with visible text, compare the rendered text with its computed accessible name. Check all sources that can override descendant text, including aria-label, aria-labelledby, title, and Fluent UI tooltips with relationship="label".

  • The computed accessible name must contain the exact localized visible text. Additional context may follow or precede it.
  • Prefer visible descendant text as the native accessible name. For a simple flat-text description, use aria-description so the visible label remains the name without adding hidden DOM content. Use aria-describedby when the description already exists in the DOM, is shared by multiple controls, contains meaningful structure, or compatibility requirements demand it. Add aria-label only when the accessible name itself needs clarification, and include the exact visible label when doing so.
  • When adding context, derive both strings from the same localized visible-label variable. Do not translate the visible and accessible labels independently because translations can drift.
  • Remember that aria-label overrides descendant text. Seeing the visible label inside the component does not mean it is included in the accessibility tree.
  • Review existing accessible-name attributes when modifying a control, even when the change does not add or alter ARIA.

For user-facing controls covered by Playwright, assert both the rendered label and computed accessible name:

const moreLabel = page.getByRole('button', { name: /^More/ });
await expect(moreLabel).toHaveText('More…');
await expect(moreLabel).toHaveAccessibleName(/^More…/);
await expect(moreLabel).toHaveAccessibleDescription('Show full description');

Avoid Hidden-Text Scroll Regressions

Do not add an absolutely positioned screen-reader-only element just to name a nearby semantic element, especially inside a scrollable table, grid, list, or panel. VoiceOver may move the virtual cursor to the hidden element's layout box and scroll the visible content out of view.

Prefer setting the accessible name or description on a rendered element with useful on-screen bounds. For text controls, this is usually the semantic element itself. For an icon or symbol inside a table cell, use a visible inline wrapper as the named accessibility node and keep the visual-only child hidden from assistive technology:

// ❌ VoiceOver may scroll to the hidden span when navigating the cell.
<td>
  <span className={styles.srOnly}>{l10n.t('Yes')}</span>
  <CheckmarkCircleFilled />
</td>

// ❌ The value is announced, but VoiceOver's visual focus indicator may disappear from the rendered symbol.
<td aria-label={l10n.t('Yes')}>
  <CheckmarkCircleFilled aria-hidden={true} />
</td>

// ✅ The accessibility node uses the visible symbol's bounds; there is no off-screen layout box to reveal.
<td>
  <span role="img" aria-label={l10n.t('Yes')} style={{ display: 'inline-flex' }}>
    <CheckmarkCircleFilled aria-hidden={true} />
  </span>
</td>

Use hidden DOM text only when the semantic relationship cannot be expressed directly. When it is necessary, use an established shared utility, anchor it to an appropriate containing block, and manually navigate the control with VoiceOver or NVDA to verify that focus and the virtual cursor do not scroll the visible content away or lose the visual focus indicator. Playwright accessible-name assertions do not detect these screen-reader-driven visual regressions.

Detection Rules

1. Tooltip Without aria-label Context

❌ Problem: Tooltip content inaccessible to screen readers

<Tooltip content="Save document to database">
  <Button aria-label="Save">Save</Button>
</Tooltip>

✅ Fix: Include tooltip in aria-label

<Tooltip content="Save document to database" relationship="description">
  <Button aria-label="Save document to database">Save</Button>
</Tooltip>

2. Missing aria-hidden (Double Announcement)

❌ Problem: Screen reader says "Collection scan Collection scan"

<Badge aria-label="Collection scan. Query is inefficient">Collection scan</Badge>

✅ Fix: Wrap visible text

<Badge aria-label="Collection scan. Query is inefficient">
  <span aria-hidden="true">Collection scan</span>
</Badge>

3. Redundant aria-label (NOT Needed)

❌ Problem: aria-label identical to visible text adds no value

<Button aria-label="Save">Save</Button>
<ToolbarButton aria-label="Validate" icon={<CheckIcon />}>Validate</ToolbarButton>

✅ Fix: Remove redundant aria-label OR make it more descriptive

<Button>Save</Button>
<ToolbarButton icon={<CheckIcon />}>Validate</ToolbarButton>

Keep aria-label only when it adds information:

<ToolbarButton aria-label="Save document to database" icon={<SaveIcon />}>
  Save
</ToolbarButton>

4. Icon-Only Button Missing aria-label

❌ Problem: No accessible name

<ToolbarButton icon={<DeleteRegular />} onClick={onDelete} />

✅ Fix: Add aria-label

<Tooltip content="Delete selected items" relationship="description">
  <ToolbarButton aria-label="Delete selected items" icon={<DeleteRegular />} onClick={onDelete} />
</Tooltip>

5. Decorative Elements Not Hidden

❌ Problem: Progress bar announced unnecessarily

<ProgressBar thickness="large" />

✅ Fix: Hide decorative elements

<ProgressBar thickness="large" aria-hidden={true} />

6. Input Missing Accessible Name

❌ Problem: SpinButton/Input without accessible name

<SpinButton value={skipValue} onChange={onSkipChange} />
<Input placeholder="Enter query..." />

✅ Fix: Add aria-label or associate with label element

<SpinButton aria-label="Skip documents" value={skipValue} onChange={onSkipChange} />
<Label htmlFor="query-input">Query</Label>
<Input id="query-input" placeholder="Enter query..." />

7. Visible Label Not in Accessible Name

❌ Problem: aria-label doesn't contain visible text (breaks voice control)

<ToolbarButton aria-label="Reload data" icon={<RefreshIcon />}>
  Refresh
</ToolbarButton>

✅ Fix: Accessible name must contain visible label exactly

<ToolbarButton aria-label="Refresh data" icon={<RefreshIcon />}>
  Refresh
</ToolbarButton>

Voice control users say "click Refresh" – only works if accessible name contains "Refresh".

8. Status Changes Not Announced

❌ Problem: Screen reader doesn't announce dynamic content

<span>{isLoading ? 'Loading...' : `${count} results`}</span>

✅ Fix: Use the Announcer component

import { Announcer } from '../../api/webview-client/accessibility';

// Announces when `when` transitions from false to true
<Announcer when={isLoading} message={l10n.t('Loading...')} />

// Dynamic message based on state
<Announcer
    when={!isLoading && documentCount !== undefined}
    message={documentCount > 0 ? l10n.t('Results found') : l10n.t('No results found')}
/>

Use for: loading states, search results, success/error messages.

9. Dialog Opens Without Focus Move

❌ Problem: Focus stays on trigger when modal opens

{
  isOpen && <Dialog>...</Dialog>;
}

✅ Fix: Move focus programmatically

const dialogRef = useRef<HTMLDivElement>(null);

useEffect(() => {
  if (isOpen) dialogRef.current?.focus();
}, [isOpen]);

{
  isOpen && (
    <Dialog ref={dialogRef} tabIndex={-1} aria-modal="true">
      ...
    </Dialog>
  );
}

10. Related Controls Without Group Label

❌ Problem: Buttons share visual label but screen reader misses context

<span>How would you rate this?</span>
<Button>👍</Button>
<Button>👎</Button>

✅ Fix: Use role="group" with aria-labelledby

<div role="group" aria-labelledby="rating-label">
  <span id="rating-label">How would you rate this?</span>
  <Button aria-label="I like it">👍</Button>
  <Button aria-label="I don't like it">👎</Button>
</div>

When to Use aria-hidden

DO use on:

  • Visible text when aria-label provides complete context
  • Decorative icons, spinners, progress bars
  • Visual separators (`|`, `—`)

DO NOT use on:

  • The only accessible content (hides it completely)
  • Interactive/focusable elements
  • Error messages or alerts

focusableBadge Pattern

For keyboard-accessible badges with tooltips:

  1. Import: `import '../components/focusableBadge/focusableBadge.scss';`
  2. Apply attributes:
<Badge tabIndex={0} className="focusableBadge" aria-label="Visible text. Tooltip details">
  <span aria-hidden="true">Visible text</span>
</Badge>

Screen Reader Announcements

Use the Announcer component for WCAG 4.1.3 (Status Messages) compliance.

import { Announcer } from '../../api/webview-client/accessibility';

Basic Usage

// Announces "AI is analyzing..." when isLoading becomes true
<Announcer when={isLoading} message={l10n.t('AI is analyzing...')} />

// Dynamic message based on state (e.g., query results)
<Announcer
    when={!isLoading && documentCount !== undefined}
    message={documentCount > 0 ? l10n.t('Results found') : l10n.t('No results found')}
/>

// With assertive politeness (default is polite)
<Announcer when={hasError} message={l10n.t('Error occurred')} politeness="assertive" />

Props

  • when: Announces when this transitions from false to true
  • message: The message to announce (use l10n.t() for localization)
  • politeness: 'assertive' (default, interrupts) or 'polite' (waits for idle)

Key Points

  • Placement doesn't matter - screen readers monitor all live regions regardless of DOM position; place near related UI for code readability
  • Store relevant state (e.g., documentCount) to derive dynamic messages
  • Use l10n.t() for messages - announcements must be localized
  • Condition resets automatically - when when goes back to false, it's ready for the next announcement
  • Prefer 'assertive' for user-initiated actions, 'polite' for background updates

Quick Checklist

  • Icon-only buttons have aria-label
  • Form inputs have associated labels or aria-label
  • Tooltip content included in aria-label
  • Visible text wrapped in aria-hidden="true" when aria-label duplicates it
  • Redundant aria-labels removed (identical to visible text)
  • Accessible names contain the exact localized visible label (for voice control)
  • VoiceOver/NVDA navigation neither scrolls visible content away nor loses its visual focus indicator
  • Decorative elements have aria-hidden={true}
  • Badges with tooltips use focusableBadge class + tabIndex={0}
  • Status updates use Announcer component
  • Focus moves to dialog/modal content when opened
  • Related controls wrapped in role="group" with aria-labelledby

References

来自 microsoft 的更多技能

oss-growth
microsoft
OSS增长黑客角色
agent-framework-azure-ai-py
microsoft
使用Microsoft Agent Framework Python SDK(agent-framework-azure-ai)构建Azure AI Foundry代理。在创建使用AzureAIAgentsProvider的持久化代理、使用托管工具(代码解释器、文件搜索、网络搜索)、集成MCP服务器、管理对话线程或实现流式响应时使用。涵盖函数工具、结构化输出和多工具代理。
development
airunway-aks-setup
microsoft
在AKS上设置AI Runway——从裸集群到运行模型。涵盖集群验证、控制器安装、GPU评估、提供商设置和首次部署。适用场景:“设置AI Runway”、“接入AKS集群”、“安装AI Runway”、“airunway设置”、“将模型部署到AKS”、“在AKS上进行GPU推理”、“在AKS上配置KAITO”、“在AKS上运行LLM”、“在AKS上使用vLLM”、“在AKS上设置模型服务”、“AI Runway控制器”。
devops
appinsights-instrumentation
microsoft
使用Azure Application Insights对Web应用进行插桩的指南。提供遥测模式、SDK设置和配置参考。适用场景:如何对应用进行插桩、App Insights SDK、遥测模式、什么是App Insights、Application Insights指南、插桩示例、APM最佳实践。
devops
applicationinsights-web-ts
microsoft
使用Application Insights JavaScript SDK(@microsoft/applicationinsights-web)为浏览器/Web应用添加检测。用于真实用户监控(RUM)——页面视图、点击、AJAX/fetch依赖项、异常、自定义事件,以及与后端OpenTelemetry追踪关联的浏览器端GenAI代理追踪。涵盖SDK加载器脚本和npm设置、框架扩展(React、React Native、Angular)、点击分析、遥测初始化器,以及从浏览器发出的代理/工具/模型跨度所遵循的OTel GenAI语义约定。
devops
azure-ai-anomalydetector-java
microsoft
使用适用于 Java 的 Azure AI 异常检测器 SDK 构建异常检测应用程序。在实现单变量/多变量异常检测、时间序列分析或 AI 驱动的监控时使用。
development
azure-ai-language-conversations-py
microsoft
使用azure-ai-language-conversations Python SDK实现对话语言理解(CLU)。当使用ConversationAnalysisClient分析对话意图和实体、构建NLP功能或将语言理解集成到应用程序中时使用。
development
azure-ai-ml-py
microsoft
Azure Machine Learning SDK v2 for Python。用于机器学习工作区、作业、模型、数据集、计算资源和管道。 触发词:“azure-ai-ml”、“MLClient”、“工作区”、“模型注册表”、“训练作业”、“数据集”。
development