Skip to content

Accessibility (A11y)

Directix provides comprehensive accessibility utilities to help you build inclusive directive experiences that work for everyone.

Overview

The accessibility module in v1.10.0 includes:

  • ARIA attribute management - Apply and manage ARIA attributes
  • Screen reader support - Announce messages to screen readers
  • Keyboard navigation - Navigate directive elements with keyboard
  • Focus management - Trap and manage focus within components

ARIA Attributes

applyAriaAttributes

Apply ARIA attributes to an element with automatic cleanup support.

typescript
import { applyAriaAttributes } from 'directix'

// Apply ARIA attributes
applyAriaAttributes(element, {
  role: 'button',
  ariaLabel: 'Submit form',
  ariaDisabled: true,
  tabIndex: 0,
})

ARIA Configuration

The ARIAConfig interface supports all standard ARIA attributes:

typescript
interface ARIAConfig {
  // Role
  role?: ARIARole // 60+ ARIA roles supported

  // State attributes
  ariaExpanded?: boolean
  ariaSelected?: boolean
  ariaChecked?: boolean
  ariaDisabled?: boolean
  ariaHidden?: boolean
  ariaBusy?: boolean
  ariaPressed?: boolean
  ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time' | boolean

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

  // Live region attributes
  ariaLive?: 'off' | 'polite' | 'assertive'
  ariaAtomic?: boolean
  ariaRelevant?: 'additions' | 'removals' | 'text' | 'all'

  // Value attributes
  ariaValueNow?: number
  ariaValueMin?: number
  ariaValueMax?: number
  ariaValueText?: string

  // Other attributes
  ariaPlaceholder?: string
  ariaRequired?: boolean
  ariaReadonly?: boolean
  ariaModal?: boolean
  tabIndex?: number
}

clearAriaAttributes

Remove all ARIA attributes from an element:

typescript
import { clearAriaAttributes } from 'directix'

clearAriaAttributes(element)

Auto-generate ARIA Config

Generate default ARIA configuration for common directive types:

typescript
import { getAutoAriaConfig } from 'directix'

// For a tooltip
const tooltipConfig = getAutoAriaConfig({
  type: 'tooltip',
  expanded: false,
})

// For a dialog
const dialogConfig = getAutoAriaConfig({
  type: 'dialog',
  label: 'Confirmation Dialog',
})

Supported types: tooltip, menu, dialog, popover, dropdown, modal, alert, region

Screen Reader Support

announce

Announce messages to screen reader users:

typescript
import { announce } from 'directix'

// Basic announcement
announce('Form submitted successfully')

// With priority (polite waits for user to pause, assertive interrupts)
announce('Error: Please fix the form', { priority: 'assertive' })

// With custom timeout
announce('Item added to cart', { timeout: 2000 })

Announcement Options

OptionTypeDefaultDescription
priority'polite' | 'assertive''polite'Announcement priority
timeoutnumber1000Time before clearing (ms)
clearbooleantrueClear message after timeout

Example: Announce Copy Success

typescript
import { announce } from 'directix'

async function copyToClipboard(text: string) {
  await navigator.clipboard.writeText(text)
  announce('Copied to clipboard')
}

Keyboard Navigation

useKeyboardNavigation

Composable for keyboard navigation within directive elements:

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>
OptionTypeDefaultDescription
nextKeysstring[]['ArrowDown', 'ArrowRight']Keys for next item
prevKeysstring[]['ArrowUp', 'ArrowLeft']Keys for previous item
selectKeysstring[]['Enter', ' ']Keys for selection
closeKeysstring[]['Escape']Keys for closing
focusTrapbooleanfalseEnable focus trap
loopbooleantrueLoop navigation
rovingTabindexbooleanfalseUse roving tabindex
returnFocusbooleantrueReturn focus on close

Focus Management

useFocusTrap

Composable for trapping focus within a container:

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('Focus trap activated'),
  onDeactivate: () => console.log('Focus trap deactivated'),
})

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

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

<template>
  <button @click="openModal">Open Modal</button>

  <div v-if="isOpen" ref="modalRef" role="dialog" aria-modal="true">
    <h2>Modal Title</h2>
    <input data-autofocus placeholder="First input" />
    <button @click="closeModal">Close</button>
  </div>
</template>

Focus Trap Options

OptionTypeDefaultDescription
initialFocusHTMLElement | string | Function-Initial focus element
allowOutsideClickboolean | FunctionfalseAllow clicks outside
escapeDeactivatesbooleantrueDeactivate on Escape
onActivateFunction-Callback on activate
onDeactivateFunction-Callback on deactivate

Best Practices

1. Always Provide Labels

typescript
// Good
applyAriaAttributes(button, {
  role: 'button',
  ariaLabel: 'Submit form',
})

// Avoid - screen readers may read nothing
element.setAttribute('role', 'button')

2. Use Live Regions Appropriately

typescript
// For status updates that can wait
announce('Saving...', { priority: 'polite' })

// For urgent errors
announce('Critical error occurred!', { priority: 'assertive' })

3. Implement Keyboard Navigation

All interactive elements should be accessible via keyboard:

  • Use useKeyboardNavigation for lists and menus
  • Use useFocusTrap for modals and dialogs
  • Provide visible focus indicators

4. Test with Screen Readers

Always test your directives with actual screen readers:

  • macOS: VoiceOver
  • Windows: NVDA or JAWS
  • Linux: Orca
  • Mobile: VoiceOver (iOS) or TalkBack (Android)

Utility Functions

FunctionDescription
applyAriaAttributesApply ARIA attributes to element
clearAriaAttributesRemove all ARIA attributes
generateAriaIdGenerate unique ARIA ID
announceAnnounce to screen readers
clearAnnouncerClear screen reader announcer
getAutoAriaConfigAuto-generate ARIA config
useKeyboardNavigationKeyboard navigation composable
useFocusTrapFocus trap composable

Released under the MIT License.