# ARIA Accessibility Attributes - Complete Reference

This reference provides comprehensive guidance for implementing ARIA (Accessible Rich Internet Applications) attributes to make web applications accessible to all users, including those using assistive technologies.

## Table of Contents

- [Overview](#overview)
- [Why ARIA?](#why-aria)
- [Semantic HTML First](#semantic-html-first)
- [ARIA Roles](#aria-roles)
- [ARIA Properties](#aria-properties)
- [ARIA States](#aria-states)
- [Live Regions](#live-regions)
- [Relationships](#relationships)
- [Keyboard Navigation](#keyboard-navigation)
- [Patterns by Component Type](#patterns-by-component-type)
- [Best Practices](#best-practices)
- [Testing](#testing)
- [WCAG Compliance](#wcag-compliance)

## Overview

ARIA attributes enhance HTML with semantic information that assistive technologies (screen readers, voice control, etc.) use to provide better user experiences for people with disabilities.

**Three categories of ARIA attributes:**
1. **Roles**: Define what an element is or does
2. **Properties**: Provide additional context and relationships
3. **States**: Indicate current state or dynamic changes

## Why ARIA?

### Legal Requirements
- **ADA Compliance**: Required for US organizations
- **WCAG 2.1**: International accessibility standard (A, AA, AAA levels)
- **Section 508**: US federal government requirement
- **European Accessibility Act**: EU requirement

### User Benefits
- **Screen reader users**: Understand page structure and interactive elements
- **Keyboard-only users**: Navigate and interact effectively
- **Voice control users**: Command elements by name/role
- **Users with cognitive disabilities**: Clearer context and relationships

### Business Benefits
- Reach wider audience (15% of population has disabilities)
- Improved SEO (accessibility correlates with search ranking)
- Better usability for all users
- Legal protection and compliance
- Enhanced brand reputation

## Semantic HTML First

**Always use semantic HTML before adding ARIA roles:**

```html
<!-- GOOD: Use native button -->
<button>Click Me</button>

<!-- BAD: Don't use div with role when native element exists -->
<div role="button" tabindex="0">Click Me</div>

<!-- GOOD: Use native navigation -->
<nav>
  <a href="/home">Home</a>
</nav>

<!-- BAD: Don't add unnecessary role to semantic element -->
<nav role="navigation">
  <a href="/home">Home</a>
</nav>
```

**First rule of ARIA**: If you can use a native HTML element or attribute with the semantics and behavior you require already built in, then do so. Only use ARIA when no native alternative exists.

## ARIA Roles

### Widget Roles (Interactive Components)

#### Button
```html
<!-- Native button (preferred) -->
<button>Click Me</button>

<!-- Custom button (when necessary) -->
<div
  role="button"
  tabindex="0"
  (click)="handleClick()"
  (keydown.enter)="handleClick()"
  (keydown.space)="handleClick()">
  Click Me
</div>
```

#### Link
```html
<!-- Native link (preferred) -->
<a href="/page">Go to Page</a>

<!-- Custom link role (rarely needed) -->
<span
  role="link"
  tabindex="0"
  (click)="navigate()">
  Go to Page
</span>
```

#### Checkbox
```html
<!-- Native checkbox (preferred) -->
<input
  type="checkbox"
  [(ngModel)]="isChecked"
  aria-label="Accept terms" />

<!-- Custom checkbox -->
<div
  role="checkbox"
  [attr.aria-checked]="isChecked"
  tabindex="0"
  (click)="toggle()">
  Accept terms
</div>
```

#### Radio
```html
<!-- Native radio (preferred) -->
<input
  type="radio"
  name="payment"
  value="credit"
  aria-label="Credit card payment" />

<!-- Custom radio group -->
<div role="radiogroup" aria-labelledby="payment-label">
  <span id="payment-label">Payment Method</span>
  <div
    role="radio"
    [attr.aria-checked]="selected === 'credit'"
    tabindex="0">
    Credit Card
  </div>
  <div
    role="radio"
    [attr.aria-checked]="selected === 'paypal'"
    tabindex="-1">
    PayPal
  </div>
</div>
```

#### Textbox
```html
<!-- Native input (preferred) -->
<input
  type="text"
  aria-label="Username"
  aria-required="true" />

<!-- Custom textbox (contenteditable) -->
<div
  role="textbox"
  contenteditable="true"
  aria-label="Rich text editor"
  aria-multiline="true">
</div>
```

#### Menu and Menuitem
```html
<div role="menu" aria-labelledby="menu-button">
  <div role="menuitem" tabindex="0">
    New File
  </div>
  <div role="menuitem" tabindex="-1">
    Open File
  </div>
  <div role="separator"></div>
  <div role="menuitem" tabindex="-1">
    Exit
  </div>
</div>
```

#### Tab, Tablist, Tabpanel
```html
<div class="tabs">
  <div role="tablist" aria-label="Content sections">
    <button
      role="tab"
      [attr.aria-selected]="activeTab === 'profile'"
      [attr.aria-controls]="'profile-panel'"
      [attr.tabindex]="activeTab === 'profile' ? 0 : -1">
      Profile
    </button>
    <button
      role="tab"
      [attr.aria-selected]="activeTab === 'settings'"
      [attr.aria-controls]="'settings-panel'"
      [attr.tabindex]="activeTab === 'settings' ? 0 : -1">
      Settings
    </button>
  </div>

  <div
    role="tabpanel"
    id="profile-panel"
    [attr.aria-hidden]="activeTab !== 'profile'"
    aria-labelledby="profile-tab">
    <!-- Profile content -->
  </div>

  <div
    role="tabpanel"
    id="settings-panel"
    [attr.aria-hidden]="activeTab !== 'settings'"
    aria-labelledby="settings-tab">
    <!-- Settings content -->
  </div>
</div>
```

### Landmark Roles (Page Structure)

#### Banner (Header)
```html
<!-- Implicit landmark (preferred) -->
<header>
  <h1>Site Title</h1>
</header>

<!-- Explicit role (when header isn't appropriate) -->
<div role="banner">
  <h1>Site Title</h1>
</div>
```

#### Navigation
```html
<!-- Implicit landmark (preferred) -->
<nav aria-label="Main navigation">
  <a href="/">Home</a>
  <a href="/about">About</a>
</nav>

<!-- Multiple navigation areas need labels -->
<nav aria-label="Primary navigation">
  <!-- Main menu -->
</nav>
<nav aria-label="Footer navigation">
  <!-- Footer menu -->
</nav>
```

#### Main
```html
<!-- Implicit landmark (preferred) -->
<main>
  <!-- Main content -->
</main>

<!-- Only one main landmark per page -->
```

#### Complementary (Aside)
```html
<aside aria-label="Related articles">
  <!-- Sidebar content -->
</aside>
```

#### Contentinfo (Footer)
```html
<footer>
  <p>&copy; 2024 Company Name</p>
</footer>
```

#### Search
```html
<form role="search" aria-label="Site search">
  <input type="search" aria-label="Search query" />
  <button type="submit">Search</button>
</form>
```

#### Region
```html
<!-- Generic landmark with label -->
<section aria-labelledby="news-heading">
  <h2 id="news-heading">Latest News</h2>
  <!-- Content -->
</section>
```

### Document Structure Roles

#### Article
```html
<article aria-labelledby="article-title">
  <h2 id="article-title">Article Title</h2>
  <!-- Article content -->
</article>
```

#### List and Listitem
```html
<!-- Native list (preferred) -->
<ul>
  <li>Item 1</li>
  <li>Item 2</li>
</ul>

<!-- Custom list (when necessary) -->
<div role="list" aria-label="Product list">
  <div role="listitem">Product 1</div>
  <div role="listitem">Product 2</div>
</div>
```

### Dialog Roles

#### Dialog
```html
<div
  role="dialog"
  aria-labelledby="dialog-title"
  aria-describedby="dialog-description"
  aria-modal="true">

  <h2 id="dialog-title">Confirm Action</h2>
  <p id="dialog-description">Are you sure you want to proceed?</p>

  <button>Confirm</button>
  <button>Cancel</button>
</div>
```

#### Alertdialog
```html
<div
  role="alertdialog"
  aria-labelledby="alert-title"
  aria-describedby="alert-description"
  aria-modal="true">

  <h2 id="alert-title">Error</h2>
  <p id="alert-description">An error occurred while saving.</p>

  <button>OK</button>
</div>
```

## ARIA Properties

### aria-label
Provides accessible name when no visible label exists:

```html
<!-- Icon button without visible text -->
<button aria-label="Close dialog">
  <i class="icon-close"></i>
</button>

<!-- Search input -->
<input
  type="search"
  aria-label="Search products"
  placeholder="Search..." />
```

### aria-labelledby
References existing element(s) as label:

```html
<!-- Form field with visible label -->
<label id="email-label">Email Address</label>
<input
  type="email"
  aria-labelledby="email-label" />

<!-- Dialog with title -->
<div
  role="dialog"
  aria-labelledby="dialog-title">
  <h2 id="dialog-title">Settings</h2>
  <!-- Dialog content -->
</div>

<!-- Multiple labels (concatenated) -->
<div
  role="button"
  aria-labelledby="btn-icon btn-text">
  <span id="btn-icon">💾</span>
  <span id="btn-text">Save</span>
</div>
```

### aria-describedby
Provides additional description:

```html
<!-- Input with help text -->
<input
  type="password"
  aria-label="Password"
  aria-describedby="password-hint" />
<span id="password-hint">
  Must be at least 8 characters
</span>

<!-- Button with additional context -->
<button
  aria-label="Delete item"
  aria-describedby="delete-warning">
  Delete
</button>
<span id="delete-warning">
  This action cannot be undone
</span>
```

### aria-required
Indicates required form fields:

```html
<input
  type="email"
  aria-label="Email address"
  aria-required="true" />

<!-- Or use native HTML5 required -->
<input
  type="email"
  aria-label="Email address"
  required />
```

### aria-placeholder
Describes expected value (use sparingly):

```html
<input
  type="text"
  aria-label="Username"
  aria-placeholder="john_doe123" />

<!-- Native placeholder is usually sufficient -->
<input
  type="text"
  aria-label="Username"
  placeholder="john_doe123" />
```

### aria-invalid
Indicates validation errors:

```html
<input
  type="email"
  aria-label="Email"
  [attr.aria-invalid]="hasError ? 'true' : null"
  aria-describedby="email-error" />

@if (hasError) {
  <span id="email-error" role="alert">
    Please enter a valid email address
  </span>
}
```

### aria-haspopup
Indicates element triggers popup:

```html
<!-- Menu button -->
<button
  aria-label="Actions menu"
  aria-haspopup="menu"
  [attr.aria-expanded]="isMenuOpen">
  Actions ▼
</button>

<!-- Dialog trigger -->
<button
  aria-label="Open settings"
  aria-haspopup="dialog">
  Settings
</button>
```

## ARIA States

### aria-expanded
Indicates expand/collapse state:

```html
<button
  (click)="toggleAccordion()"
  [attr.aria-expanded]="isExpanded"
  aria-controls="panel-content">
  Toggle Panel
</button>

<div
  id="panel-content"
  [attr.aria-hidden]="!isExpanded">
  <!-- Panel content -->
</div>
```

### aria-selected
Indicates selection in lists or tabs:

```html
@for (tab of tabs; track tab.id) {
  <button
    role="tab"
    [attr.aria-selected]="activeTab === tab.id"
    [attr.tabindex]="activeTab === tab.id ? 0 : -1">
    {{ tab.label }}
  </button>
}
```

### aria-checked
Indicates checkbox/radio state:

```html
<!-- Native checkbox handles this automatically -->
<input
  type="checkbox"
  [(ngModel)]="isChecked" />

<!-- Custom checkbox needs explicit aria-checked -->
<div
  role="checkbox"
  [attr.aria-checked]="isChecked"
  tabindex="0">
  {{ label }}
</div>
```

### aria-disabled
Indicates disabled state:

```html
<button
  [disabled]="!isValid"
  [attr.aria-disabled]="!isValid">
  Submit
</button>

<!-- For elements that don't support disabled attribute -->
<div
  role="button"
  [attr.aria-disabled]="!canPerformAction"
  [class.disabled]="!canPerformAction">
  Action
</div>
```

### aria-hidden
Hides element from assistive technology:

```html
<!-- Hide decorative icons -->
<button>
  <i class="icon-save" aria-hidden="true"></i>
  Save
</button>

<!-- Hide collapsed content -->
<div [attr.aria-hidden]="!isVisible">
  <!-- Content -->
</div>
```

### aria-pressed
Indicates toggle button state:

```html
<button
  [attr.aria-pressed]="isActive"
  (click)="toggle()">
  {{ isActive ? 'Active' : 'Inactive' }}
</button>
```

### aria-current
Indicates current item in set:

```html
<!-- Current page in navigation -->
<nav>
  <a href="/home" [attr.aria-current]="currentPage === 'home' ? 'page' : null">
    Home
  </a>
  <a href="/about" [attr.aria-current]="currentPage === 'about' ? 'page' : null">
    About
  </a>
</nav>

<!-- Current step in process -->
<ol>
  @for (step of steps; track step.id) {
    <li [attr.aria-current]="currentStep === step.id ? 'step' : null">
      {{ step.label }}
    </li>
  }
</ol>
```

## Live Regions

Live regions announce dynamic content changes to screen readers:

### aria-live
```html
<!-- Polite: Waits for pause in speech -->
<div aria-live="polite" aria-atomic="true">
  {{ statusMessage }}
</div>

<!-- Assertive: Interrupts immediately -->
<div aria-live="assertive" aria-atomic="true">
  {{ criticalError }}
</div>
```

### role="alert"
Implicit aria-live="assertive":

```html
<div role="alert">
  {{ errorMessage }}
</div>
```

### role="status"
Implicit aria-live="polite":

```html
<div role="status">
  {{ successMessage }}
</div>
```

### aria-atomic
Controls whether entire region or just changes are announced:

```html
<!-- Announce entire region content -->
<div aria-live="polite" aria-atomic="true">
  Loading: {{ progress }}%
</div>

<!-- Only announce what changed -->
<div aria-live="polite" aria-atomic="false">
  Items: {{ itemCount }}
</div>
```

### aria-relevant
Controls what changes trigger announcements:

```html
<div
  aria-live="polite"
  aria-relevant="additions removals text">
  <!-- Announcements for additions, removals, and text changes -->
</div>
```

## Relationships

### aria-controls
Identifies controlled elements:

```html
<button
  (click)="togglePanel()"
  aria-controls="panel-content"
  [attr.aria-expanded]="isPanelOpen">
  Toggle
</button>

<div id="panel-content">
  <!-- Panel content -->
</div>
```

### aria-owns
Defines parent-child relationship when DOM structure doesn't:

```html
<div role="tree" aria-owns="child1 child2">
  <!-- Tree root -->
</div>

<!-- Children elsewhere in DOM -->
<div id="child1" role="treeitem">Child 1</div>
<div id="child2" role="treeitem">Child 2</div>
```

### aria-activedescendant
Indicates active descendant in composite widget:

```html
<div
  role="listbox"
  [attr.aria-activedescendant]="'option-' + activeIndex"
  tabindex="0">

  @for (option of options; track option.id; let idx = $index) {
    <div
      role="option"
      [id]="'option-' + idx"
      [class.active]="idx === activeIndex">
      {{ option.label }}
    </div>
  }
</div>
```

### aria-flowto
Defines reading order when visual order differs:

```html
<div id="section1" aria-flowto="section3">
  Section 1
</div>

<div id="section2">
  Section 2 (sidebar)
</div>

<div id="section3">
  Section 3 (continues from section 1)
</div>
```

## Keyboard Navigation

Proper keyboard navigation is essential for accessibility:

### Tabindex
```html
<!-- tabindex="0": Include in tab order (natural position) -->
<div role="button" tabindex="0">Clickable</div>

<!-- tabindex="-1": Focusable programmatically, not in tab order -->
<div role="menuitem" tabindex="-1">Menu Item</div>

<!-- tabindex="1+": Custom tab order (avoid unless necessary) -->
<input tabindex="1" />
<input tabindex="2" />
```

### Keyboard Event Handlers
```html
<div
  role="button"
  tabindex="0"
  (click)="handleAction()"
  (keydown.enter)="handleAction()"
  (keydown.space)="handleAction()">
  Custom Button
</div>

<!-- Arrow key navigation -->
<div
  role="listbox"
  tabindex="0"
  (keydown.arrowDown)="moveNext()"
  (keydown.arrowUp)="movePrevious()"
  (keydown.home)="moveFirst()"
  (keydown.end)="moveLast()">
  <!-- List items -->
</div>
```

### Focus Management
```typescript
// Move focus programmatically
@ViewChild('dialogElement') dialogElement!: ElementRef;

openDialog() {
  this.showDialog = true;
  setTimeout(() => {
    this.dialogElement.nativeElement.focus();
  });
}
```

## Patterns by Component Type

### Form with Validation
```html
<form>
  <div class="field">
    <label id="username-label" for="username">Username</label>
    <input
      id="username"
      type="text"
      [(ngModel)]="username"
      aria-labelledby="username-label"
      aria-required="true"
      [attr.aria-invalid]="usernameError ? 'true' : null"
      aria-describedby="username-hint username-error" />
    <span id="username-hint">3-20 characters</span>
    @if (usernameError) {
      <span id="username-error" role="alert">
        {{ usernameError }}
      </span>
    }
  </div>
</form>
```

### Modal Dialog
```html
<div
  *ngIf="isOpen"
  class="modal-overlay"
  (click)="closeDialog()">

  <div
    class="modal"
    role="dialog"
    aria-modal="true"
    aria-labelledby="modal-title"
    aria-describedby="modal-description"
    (click)="$event.stopPropagation()"
    #modalElement
    tabindex="-1">

    <h2 id="modal-title">Confirm Delete</h2>
    <p id="modal-description">
      Are you sure you want to delete this item?
    </p>

    <button
      (click)="confirm()"
      aria-label="Confirm deletion">
      Delete
    </button>

    <button
      (click)="cancel()"
      aria-label="Cancel deletion">
      Cancel
    </button>
  </div>
</div>
```

### Accordion
```html
@for (section of sections; track section.id) {
  <div class="accordion-item">
    <button
      (click)="toggle(section.id)"
      [attr.aria-expanded]="section.expanded"
      aria-controls="panel-{{ section.id }}">
      {{ section.title }}
    </button>

    <div
      id="panel-{{ section.id }}"
      [attr.aria-hidden]="!section.expanded"
      role="region"
      [attr.aria-labelledby]="'heading-' + section.id">
      {{ section.content }}
    </div>
  </div>
}
```

### Accessible Data Table
```html
<table role="table" aria-labelledby="table-title">
  <caption id="table-title">Customer Orders</caption>
  <thead>
    <tr>
      <th scope="col">Order ID</th>
      <th scope="col">Customer</th>
      <th scope="col">Total</th>
      <th scope="col">Actions</th>
    </tr>
  </thead>
  <tbody>
    @for (order of orders; track order.id) {
      <tr>
        <td>{{ order.id }}</td>
        <td>{{ order.customer }}</td>
        <td>{{ order.total | currency }}</td>
        <td>
          <button [attr.aria-label]="'View order ' + order.id">
            View
          </button>
        </td>
      </tr>
    }
  </tbody>
</table>
```

### Toast Notifications
```html
<div
  class="toast"
  role="status"
  aria-live="polite"
  aria-atomic="true"
  *ngIf="showToast">
  {{ toastMessage }}
</div>

<!-- For errors, use role="alert" -->
<div
  class="toast error"
  role="alert"
  aria-live="assertive"
  aria-atomic="true"
  *ngIf="showError">
  {{ errorMessage }}
</div>
```

### Loading Indicator
```html
<div
  class="loading"
  role="status"
  aria-live="polite"
  aria-label="Loading content">
  <span aria-hidden="true">⏳</span>
  <span>Loading...</span>
</div>
```

## Best Practices

### Do's ✅

1. **Use semantic HTML first**
   - Prefer `<button>` over `<div role="button">`
   - Use native form controls when possible
   - Leverage built-in accessibility of HTML elements

2. **Provide text alternatives**
   - All images need `alt` text
   - Icon-only buttons need `aria-label`
   - Complex controls need `aria-describedby`

3. **Maintain keyboard accessibility**
   - All interactive elements must be keyboard accessible
   - Implement proper focus management
   - Use `tabindex` appropriately

4. **Keep states in sync**
   - Visual state must match ARIA state
   - Update `aria-expanded`, `aria-selected`, etc. when changing UI

5. **Test with assistive technology**
   - Screen readers (NVDA, JAWS, VoiceOver)
   - Keyboard-only navigation
   - Voice control software

6. **Announce dynamic changes**
   - Use live regions for status updates
   - Announce errors and success messages
   - Alert users to important changes

### Don'ts ❌

1. **Don't override native semantics unnecessarily**
   - ❌ `<button role="link">` (use `<a>` instead)
   - ❌ `<h1 role="button">` (use `<button>` or style differently)

2. **Don't use ARIA to fix bad HTML**
   - Fix the HTML structure instead
   - Use semantic elements properly

3. **Don't hide focusable elements with aria-hidden**
   - ❌ `<button aria-hidden="true">` (removes from all users)
   - Use `visibility: hidden` or `display: none` instead

4. **Don't create keyboard traps**
   - Ensure users can navigate out of components
   - Implement proper focus management in modals

5. **Don't forget mobile accessibility**
   - Touch targets should be at least 44x44 pixels
   - Ensure gestures have keyboard equivalents
   - Test with mobile screen readers

## Testing

### Automated Testing Tools
- **axe DevTools**: Browser extension for accessibility audits
- **WAVE**: Web accessibility evaluation tool
- **Lighthouse**: Built into Chrome DevTools
- **Pa11y**: Command-line accessibility testing

### Manual Testing
1. **Keyboard navigation**
   - Tab through all interactive elements
   - Use arrow keys where appropriate
   - Test Escape, Enter, and Space keys

2. **Screen reader testing**
   - **Windows**: NVDA (free), JAWS (commercial)
   - **macOS**: VoiceOver (built-in)
   - **Mobile**: TalkBack (Android), VoiceOver (iOS)

3. **Visual testing**
   - Zoom to 200%
   - Test high contrast mode
   - Check color contrast ratios

### Unit Test Example
```typescript
describe('Accessibility', () => {
  it('should have proper ARIA attributes', () => {
    const button = fixture.nativeElement.querySelector('[data-what="submit-button"]');

    expect(button.getAttribute('aria-label')).toBe('Submit form');
    expect(button.getAttribute('role')).toBeFalsy(); // Native button, no role needed
  });

  it('should toggle aria-expanded', () => {
    const toggle = fixture.nativeElement.querySelector('[data-what="toggle-button"]');

    expect(toggle.getAttribute('aria-expanded')).toBe('false');

    toggle.click();
    fixture.detectChanges();

    expect(toggle.getAttribute('aria-expanded')).toBe('true');
  });
});
```

## WCAG Compliance

### WCAG 2.1 Levels
- **Level A**: Minimum accessibility (legal requirement in many jurisdictions)
- **Level AA**: Commonly targeted level (includes color contrast, text sizing)
- **Level AAA**: Highest level (may not be achievable for all content)

### Key WCAG Requirements

#### Perceivable
- Provide text alternatives for non-text content
- Ensure sufficient color contrast (4.5:1 for normal text, 3:1 for large text)
- Make content available to assistive technologies

#### Operable
- Make all functionality keyboard accessible
- Provide enough time for users to read and interact
- Don't use content that causes seizures (flashing)
- Help users navigate and find content

#### Understandable
- Make text readable and understandable
- Make content appear and operate in predictable ways
- Help users avoid and correct mistakes

#### Robust
- Maximize compatibility with assistive technologies
- Use valid, well-formed HTML
- Ensure ARIA is used correctly

### Color Contrast Checker
```html
<!-- Minimum contrast ratios (WCAG AA) -->
<!-- Normal text (< 18pt): 4.5:1 -->
<!-- Large text (≥ 18pt or ≥ 14pt bold): 3:1 -->
<!-- UI components and graphics: 3:1 -->

<button class="primary">
  <!-- Ensure button text meets contrast requirements -->
  Click Me
</button>
```

## Related Documentation

- **[E2E Testing Attributes](e2e-attributes.md)** - E2E testing guidance
- **[Combined Patterns](combined-patterns.md)** - Examples with E2E + ARIA together
- **[WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)** - Official WCAG quick reference
- **[ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/)** - Official ARIA patterns

## Summary

ARIA attributes are essential for:
- ✅ Making applications accessible to all users
- ✅ Meeting legal requirements (ADA, WCAG, Section 508)
- ✅ Providing excellent user experience with assistive technology
- ✅ Improving SEO and usability for everyone
- ✅ Demonstrating social responsibility and inclusivity

**Always use semantic HTML first, then enhance with ARIA when needed.**
