Skip to content

无障碍 (A11y)

Directix 提供全面的无障碍工具,帮助您构建适用于所有人的包容性指令体验。

概述

v1.10.0 的无障碍模块包括:

  • ARIA 属性管理 - 应用和管理 ARIA 属性
  • 屏幕阅读器支持 - 向屏幕阅读器公告消息
  • 键盘导航 - 使用键盘导航指令元素
  • 焦点管理 - 在组件内陷阱和管理焦点

ARIA 属性

applyAriaAttributes

将 ARIA 属性应用到元素,支持自动清理。

typescript
import { applyAriaAttributes } from 'directix'

// 应用 ARIA 属性
applyAriaAttributes(element, {
  role: 'button',
  ariaLabel: '提交表单',
  ariaDisabled: true,
  tabIndex: 0,
})

ARIA 配置

ARIAConfig 接口支持所有标准 ARIA 属性:

typescript
interface ARIAConfig {
  // 角色
  role?: ARIARole // 支持 60+ ARIA 角色

  // 状态属性
  ariaExpanded?: boolean
  ariaSelected?: boolean
  ariaChecked?: boolean
  ariaDisabled?: boolean
  ariaHidden?: boolean
  ariaBusy?: boolean
  ariaPressed?: boolean
  ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time' | boolean

  // 属性属性
  ariaLabel?: string
  ariaLabelledBy?: string
  ariaDescribedBy?: string
  ariaControls?: string
  ariaOwns?: string
  ariaHasPopup?: 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | 'tooltip' | boolean

  // 实时区域属性
  ariaLive?: 'off' | 'polite' | 'assertive'
  ariaAtomic?: boolean
  ariaRelevant?: 'additions' | 'removals' | 'text' | 'all'

  // 值属性
  ariaValueNow?: number
  ariaValueMin?: number
  ariaValueMax?: number
  ariaValueText?: string

  // 其他属性
  ariaPlaceholder?: string
  ariaRequired?: boolean
  ariaReadonly?: boolean
  ariaModal?: boolean
  tabIndex?: number
}

clearAriaAttributes

移除元素上的所有 ARIA 属性:

typescript
import { clearAriaAttributes } from 'directix'

clearAriaAttributes(element)

自动生成 ARIA 配置

为常见指令类型生成默认 ARIA 配置:

typescript
import { getAutoAriaConfig } from 'directix'

// 工具提示
const tooltipConfig = getAutoAriaConfig({
  type: 'tooltip',
  expanded: false,
})

// 对话框
const dialogConfig = getAutoAriaConfig({
  type: 'dialog',
  label: '确认对话框',
})

支持类型:tooltipmenudialogpopoverdropdownmodalalertregion

屏幕阅读器支持

announce

向屏幕阅读器用户公告消息:

typescript
import { announce } from 'directix'

// 基本公告
announce('表单提交成功')

// 带优先级(polite 等待用户暂停,assertive 立即打断)
announce('错误:请修正表单', { priority: 'assertive' })

// 自定义超时时间
announce('已添加到购物车', { timeout: 2000 })

公告选项

选项类型默认值说明
priority'polite' | 'assertive''polite'公告优先级
timeoutnumber1000清除消息前的等待时间(毫秒)
clearbooleantrue超时后清除消息

键盘导航

useKeyboardNavigation

指令元素键盘导航的组合式函数:

vue
<script setup>
import { ref, onMounted } from 'vue'
import { useKeyboardNavigation } from 'directix'

const containerRef = ref(null)
const items = ref([])

const { focusedIndex, bind, focusNext, focusPrev } = useKeyboardNavigation({
  focusTrap: true,
  loop: true,
  rovingTabindex: true,
})

onMounted(() => {
  if (containerRef.value) {
    bind(containerRef.value, items.value)
  }
})
</script>

<template>
  <div ref="containerRef" role="listbox">
    <div
      v-for="(item, index) in items"
      :key="item.id"
      ref="items"
      role="option"
      @click="selectItem(index)"
    >
      {{ item.label }}
    </div>
  </div>
</template>

导航配置

选项类型默认值说明
nextKeysstring[]['ArrowDown', 'ArrowRight']下一个元素的按键
prevKeysstring[]['ArrowUp', 'ArrowLeft']上一个元素的按键
selectKeysstring[]['Enter', ' ']选择按键
closeKeysstring[]['Escape']关闭按键
focusTrapbooleanfalse启用焦点陷阱
loopbooleantrue循环导航
rovingTabindexbooleanfalse使用漫游 tabindex
returnFocusbooleantrue关闭时返回焦点

焦点管理

useFocusTrap

在容器内陷阱焦点的组合式函数:

vue
<script setup>
import { ref } from 'vue'
import { useFocusTrap } from 'directix'

const modalRef = ref(null)
const isOpen = ref(false)

const { activate, deactivate, isActive } = useFocusTrap(modalRef, {
  initialFocus: '[data-autofocus]',
  returnFocus: true,
  escapeDeactivates: true,
  onActivate: () => console.log('焦点陷阱已激活'),
  onDeactivate: () => console.log('焦点陷阱已取消'),
})

function openModal() {
  isOpen.value = true
  activate()
}

function closeModal() {
  deactivate()
  isOpen.value = false
}
</script>

<template>
  <button @click="openModal">打开模态框</button>

  <div v-if="isOpen" ref="modalRef" role="dialog" aria-modal="true">
    <h2>模态框标题</h2>
    <input data-autofocus placeholder="第一个输入框" />
    <button @click="closeModal">关闭</button>
  </div>
</template>

焦点陷阱选项

选项类型默认值说明
initialFocusHTMLElement | string | Function-初始焦点元素
allowOutsideClickboolean | Functionfalse允许外部点击
escapeDeactivatesbooleantrueEscape 键取消
onActivateFunction-激活回调
onDeactivateFunction-取消回调

最佳实践

1. 始终提供标签

typescript
// 好的做法
applyAriaAttributes(button, {
  role: 'button',
  ariaLabel: '提交表单',
})

// 避免 - 屏幕阅读器可能无法读取任何内容
element.setAttribute('role', 'button')

2. 正确使用实时区域

typescript
// 可等待的状态更新
announce('正在保存...', { priority: 'polite' })

// 紧急错误
announce('发生严重错误!', { priority: 'assertive' })

3. 实现键盘导航

所有交互元素都应可通过键盘访问:

  • 列表和菜单使用 useKeyboardNavigation
  • 模态框和对话框使用 useFocusTrap
  • 提供可见的焦点指示器

4. 使用屏幕阅读器测试

始终使用实际屏幕阅读器测试您的指令:

  • macOS: VoiceOver
  • Windows: NVDA 或 JAWS
  • Linux: Orca
  • 移动端: VoiceOver (iOS) 或 TalkBack (Android)

工具函数

函数说明
applyAriaAttributes将 ARIA 属性应用到元素
clearAriaAttributes移除所有 ARIA 属性
generateAriaId生成唯一 ARIA ID
announce向屏幕阅读器公告
clearAnnouncer清除屏幕阅读器公告器
getAutoAriaConfig自动生成 ARIA 配置
useKeyboardNavigation键盘导航组合式函数
useFocusTrap焦点陷阱组合式函数

基于 MIT 许可发布