Labs ICT

HTML ARIA Roles

Accessible Rich Internet Applications (ARIA) is a set of attributes that define ways to make web content and web applications more accessible to people with disabilities. ARIA supplements HTML so that semantic information can be provided when HTML semantics are not sufficient, enabling assistive technologies to convey appropriate information to users. While semantic HTML should always be the first choice, ARIA provides powerful tools for creating accessible dynamic content and complex user interfaces.

What is ARIA?

Definition and Purpose

ARIA (Accessible Rich Internet Applications) is a technical specification that provides a framework for adding accessibility information to web elements that cannot be adequately expressed with native HTML alone. It bridges the gap between complex web applications and assistive technologies, ensuring that all users can understand and interact with dynamic content.

When to Use ARIA

  • Dynamic Content: When content changes without page reload
  • Custom Controls: When creating non-standard UI elements
  • Complex Widgets: For tabs, menus, dialogs, and trees
  • Landmark Navigation: When HTML5 semantic elements aren't sufficient
  • State Management: To communicate element states to assistive technologies
  • Relationship Definition: To connect related elements

ARIA Categories

<!-- ARIA Categories -->
<!-- Roles: Define what an element is -->
<div role="navigation">...</div>

<!-- Properties: Define characteristics of an element -->
<input aria-label="Search products">

<!-- States: Define current condition of an element -->
<button aria-expanded="false">Menu</button>

ARIA Roles

ARIA roles define the purpose and behavior of elements for assistive technologies:

Landmark Roles

Landmark roles help users navigate through different sections of a page:

<!-- Landmark roles for page structure -->
<header role="banner">
  <h1>Site Header</h1>
</header>

<nav role="navigation" aria-label="Main navigation">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

<main role="main">
  <h1>Main Content</h1>
  <p>Primary page content...</p>
</main>

<aside role="complementary" aria-label="Sidebar">
  <h2>Related Information</h2>
  <p>Secondary content...</p>
</aside>

<footer role="contentinfo">
  <p>Copyright and legal information</p>
</footer>

Widget Roles

The widget roles define the purpose and behavior of interactive elements:

<!-- Common widget roles -->
<!-- Tablist -->
<div role="tablist">
  <button role="tab" aria-selected="true">Tab 1</button>
  <button role="tab" aria-selected="false">Tab 2</button>
</div>

<!-- Menu -->
<ul role="menu">
  <li role="menuitem"><a href="#">Item 1</a></li>
  <li role="menuitem"><a href="#">Item 2</a></li>
</ul>

<!-- Dialog -->
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
  <h2 id="dialog-title">Dialog Title</h2>
  <p>Dialog content...</p>
</div>

Document Structure Roles

Document structure roles provide semantic meaning to content sections:

<!-- Document structure roles -->
<article role="article">
  <h1>Article Title</h1>
  <p>Article content...</p>
</article>

<section role="region" aria-labelledby="region-title">
  <h2 id="region-title">Region Title</h2>
  <p>Region content...</p>
</section>

<div role="group" aria-labelledby="group-title">
  <h3 id="group-title">Group Title</h3>
  <p>Grouped content...</p>
</div>

ARIA Properties

ARIA properties provide additional information about elements to assistive technologies:

Labeling Properties

ARIA labeling properties provide accessible names for elements:

<!-- aria-label: Direct label for an element -->
<button aria-label="Close dialog">ร—</button>
<input type="search" aria-label="Search products">

<!-- aria-labelledby: Reference to another element's text -->
<div id="dialog-title">User Profile</div>
<div role="dialog" aria-labelledby="dialog-title">
  <p>Dialog content...</p>
</div>

<!-- Multiple labels -->
<h2 id="form-title">Registration Form</h2>
<p id="form-desc">Create your account to get started</p>
<form aria-labelledby="form-title" aria-describedby="form-desc">
  <!-- Form fields -->
</form>

Descriptive Properties

ARIA descriptive properties provide additional information about elements:

<!-- aria-describedby: Reference to descriptive text -->
<input type="email" 
       aria-describedby="email-help email-error">
<div id="email-help">Enter your work email address</div>
<div id="email-error" hidden>Invalid email format</div>

<!-- aria-placeholder: Accessible placeholder text -->
<input type="text" 
       aria-placeholder="Search for products...">

<!-- aria-roledescription: Custom role description -->
<div role="group" aria-roledescription="Product filter options">
  <!-- Filter options -->
</div>

Relationship Properties

ARIA relationship properties define connections between elements:

<!-- aria-owns: Indicates ownership of elements -->
<div role="grid" aria-owns="grid-cell-1 grid-cell-2">
  <!-- Grid content -->
</div>
<div id="grid-cell-1">Cell 1</div>
<div id="grid-cell-2">Cell 2</div>

<!-- aria-controls: Element controlled by another -->
<button aria-controls="menu-panel" aria-expanded="false">
  Toggle Menu
</button>
<div id="menu-panel" hidden>
  <!-- Menu content -->
</div>

<!-- aria-flowto: Suggests next reading order -->
<div id="section-1" aria-flowto="section-2">Section 1</div>
<div id="section-2">Section 2</div>

ARIA States

ARIA states provide information about the current condition of elements to assistive technologies:

Common ARIA States

States communicate the current condition of elements to assistive technologies:

<!-- aria-expanded: For collapsible content -->
<button aria-expanded="false" aria-controls="menu">
  Menu
</button>
<ul id="menu" hidden>
  <li><a href="#">Item 1</a></li>
</ul>

<!-- aria-selected: For selection in lists and tabs -->
<div role="tablist">
  <button role="tab" aria-selected="true">Active Tab</button>
  <button role="tab" aria-selected="false">Inactive Tab</button>
</div>

<!-- aria-checked: For checkboxes and radio buttons -->
<div role="checkbox" aria-checked="false" tabindex="0">
  Option 1
</div>
<div role="checkbox" aria-checked="true" tabindex="0">
  Option 2
</div>

<!-- aria-disabled: For disabled elements -->
<button aria-disabled="true">Disabled Button</button>

<!-- aria-hidden: To hide elements from assistive technologies -->
<div aria-hidden="true">Loading spinner...</div>

Dynamic St ate Updates

ARIA states can be updated dynamically using JavaScript:

<!-- JavaScript to update ARIA states -->
<script>
function toggleMenu(button, menu) {
  const isExpanded = button.getAttribute('aria-expanded') === 'true';
  
  // Update ARIA states
  button.setAttribute('aria-expanded', !isExpanded);
  menu.hidden = isExpanded;
  
  // Update button text
  button.textContent = isExpanded ? 'Show Menu' : 'Hide Menu';
}

// Usage
const menuButton = document.getElementById('menu-button');
const menuPanel = document.getElementById('menu-panel');

menuButton.addEventListener('click', () => {
  toggleMenu(menuButton, menuPanel);
});
</script>

Live Regions

Live regions are areas of the page that contain dynamic content which should be announced to screen reader users:

What are Live Regions?

Live regions announce dynamic content changes to screen reader users without requiring focus:

Live Region Attributes

Live regions can be configured with the following attributes:

<!-- aria-live: Politely announces changes -->
<div aria-live="polite" id="status-messages">
  <!-- Status messages appear here -->
</div>

<!-- aria-live="assertive": Immediately announces important changes -->
<div aria-live="assertive" id="error-messages">
  <!-- Error messages appear here -->
</div>

<!-- aria-live="off": No announcements (default) -->
<div aria-live="off">No announcements</div>

<!-- aria-atomic: Whether entire region should be announced -->
<div aria-live="polite" aria-atomic="true">
  <span id="status">Status: </span>
  <span id="message">Ready</span>
</div>

<!-- aria-relevant: What changes should be announced -->
<div aria-live="polite" aria-relevant="additions text">
  <!-- Only additions and text changes are announced -->
</div>

Practical Live Region Examples

Here are some common use cases for live regions:

<!-- Form validation messages -->
<form>
  <label for="email">Email:</label>
  <input type="email" id="email" required>
  
  <div aria-live="polite" id="validation-message"></div>
</form>

<!-- Loading status -->
<button id="load-data">Load Data</button>
<div aria-live="polite" id="loading-status"></div>

<!-- Search results count -->
<input type="search" id="search" placeholder="Search...">
<div aria-live="polite" id="results-count"></div>

<script>
// Form validation
document.getElementById('email').addEventListener('blur', function() {
  const validationMessage = document.getElementById('validation-message');
  if (this.validity.valid) {
    validationMessage.textContent = 'Email is valid';
  } else {
    validationMessage.textContent = 'Please enter a valid email address';
  }
});

// Loading status
document.getElementById('load-data').addEventListener('click', function() {
  const status = document.getElementById('loading-status');
  status.textContent = 'Loading...';
  
  setTimeout(() => {
    status.textContent = 'Data loaded successfully';
  }, 2000);
});

// Search results
document.getElementById('search').addEventListener('input', function() {
  const resultsCount = document.getElementById('results-count');
  const query = this.value;
  
  if (query.length > 0) {
    resultsCount.textContent = `Searching for "${query}"...`;
    // Simulate search
    setTimeout(() => {
      resultsCount.textContent = `Found 5 results for "${query}"`;
    }, 500);
  } else {
    resultsCount.textContent = '';
  }
});
</script>

Common ARIA Patterns

Here are some common ARIA patterns used to create accessible user interfaces:

Accessible Tabs

The following example demonstrates how to implement accessible tabs using ARIA attributes:

<!-- ARIA tabs implementation -->
<div role="tablist" aria-label="Product Categories">
  <button role="tab" 
          aria-selected="true" 
          aria-controls="electronics-panel" 
          id="electronics-tab">
    Electronics
  </button>
  <button role="tab" 
          aria-selected="false" 
          aria-controls="clothing-panel" 
          id="clothing-tab">
    Clothing
  </button>
</div>

<div role="tabpanel" 
     id="electronics-panel" 
     aria-labelledby="electronics-tab"
     tabindex="0">
  <h3>Electronics</h3>
  <p>Electronic products content...</p>
</div>

<div role="tabpanel" 
     id="clothing-panel" 
     aria-labelledby="clothing-tab"
     tabindex="0"
     hidden>
  <h3>Clothing</h3>
  <p>Clothing products content...</p>
</div>

Accessible Menu

The following example demonstrates how to implement an accessible menu using ARIA attributes:

<!-- ARIA menu implementation -->
<button id="menu-button" 
        aria-haspopup="true" 
        aria-expanded="false">
  Menu
</button>

<ul role="menu" 
    aria-labelledby="menu-button"
    id="menu-panel"
    hidden>
  <li role="none">
    <a role="menuitem" href="#">Home</a>
  </li>
  <li role="none">
    <a role="menuitem" href="#">About</a>
  </li>
  <li role="none">
    <a role="menuitem" href="#">Contact</a>
  </li>
</ul>

Accessible Dialog

The following example demonstrates how to implement an accessible dialog using ARIA attributes:

<!-- ARIA dialog implementation -->
<button id="open-dialog">Open Dialog</button>

<div role="dialog" 
     aria-modal="true" 
     aria-labelledby="dialog-title"
     id="dialog"
     hidden>
  <h2 id="dialog-title">Confirmation</h2>
  <p>Are you sure you want to delete this item?</p>
  
  <button id="confirm-delete">Delete</button>
  <button id="cancel-delete">Cancel</button>
</div>

Accessible Accordion

<!-- ARIA accordion implementation -->
<div class="accordion">
  <h3>
    <button aria-expanded="false" 
            aria-controls="section1-content">
      Section 1
    </button>
  </h3>
  <div id="section1-content" hidden>
    <p>Section 1 content...</p>
  </div>
  
  <h3>
    <button aria-expanded="false" 
            aria-controls="section2-content">
      Section 2
    </button>
  </h3>
  <div id="section2-content" hidden>
    <p>Section 2 content...</p>
  </div>
</div>

ARIA Best Practices

Here are some best practices for using ARIA attributes effectively:

First Rule of ARIA

Don't use ARIA if you can use native HTML!

Always prefer semantic HTML elements over ARIA roles:

<!-- Bad: Using ARIA instead of native HTML -->
<div role="button" tabindex="0">Click me</div>
<div role="navigation">...</div>
<div role="img" alt="Description">...</div>

<!-- Good: Using native HTML elements -->
<button>Click me</button>
<nav>...</nav>
<img src="image.jpg" alt="Description">

Advanced ARIA Techniques

Here are some advanced techniques for implementing ARIA attributes:

Complex Grid Implementation

The following example demonstrates how to implement an accessible grid using ARIA attributes:

<!-- ARIA grid for data tables -->
<div role="grid" aria-label="Product inventory">
  <div role="rowgroup">
    <div role="row" aria-rowindex="1">
      <div role="columnheader" aria-colindex="1">Product</div>
      <div role="columnheader" aria-colindex="2">Price</div>
      <div role="columnheader" aria-colindex="3">Stock</div>
    </div>
  </div>
  
  <div role="rowgroup">
    <div role="row" aria-rowindex="2" aria-selected="false">
      <div role="gridcell" aria-colindex="1">Laptop</div>
      <div role="gridcell" aria-colindex="2">$999</div>
      <div role="gridcell" aria-colindex="3">15</div>
    </div>
    
    <div role="row" aria-rowindex="3" aria-selected="true">
      <div role="gridcell" aria-colindex="1">Mouse</div>
      <div role="gridcell" aria-colindex="2">$25</div>
      <div role="gridcell" aria-colindex="3">50</div>
    </div>
  </div>
</div>

Custom Component with Full ARIA Support

The following example demonstrates how to implement an accessible custom rating component using ARIA attributes:

<!-- Custom rating component -->
<div class="rating" 
     role="slider" 
     aria-label="Product rating" 
     aria-valuemin="1" 
     aria-valuemax="5" 
     aria-valuenow="3"
     aria-valuetext="3 out of 5 stars"
     tabindex="0">
  <span class="stars">
    <span aria-hidden="true"></span>
    <span aria-hidden="true"></span>
    <span aria-hidden="true"></span>
    <span aria-hidden="true"></span>
    <span aria-hidden="true"></span>
  </span>
  <span class="rating-text">3 out of 5</span>
</div>

<script>
class AccessibleRating {
  constructor(element) {
    this.element = element;
    this.stars = element.querySelectorAll('.stars span');
    this.ratingText = element.querySelector('.rating-text');
    this.currentRating = 3;
    this.maxRating = 5;
    
    this.init();
  }
  
  init() {
    this.stars.forEach((star, index) => {
      star.addEventListener('click', () => this.setRating(index + 1));
      star.addEventListener('mouseenter', () => this.previewRating(index + 1));
    });
    
    this.element.addEventListener('mouseleave', () => this.updateDisplay());
    
    // Keyboard support
    this.element.addEventListener('keydown', (e) => {
      if (e.key === 'ArrowLeft') {
        this.setRating(Math.max(1, this.currentRating - 1));
      } else if (e.key === 'ArrowRight') {
        this.setRating(Math.min(this.maxRating, this.currentRating + 1));
      }
    });
  }
  
  setRating(rating) {
    this.currentRating = rating;
    this.updateDisplay();
    this.updateARIA();
  }
  
  previewRating(rating) {
    this.updateStars(rating);
    this.ratingText.textContent = `${rating} out of 5`;
  }
  
  updateDisplay() {
    this.updateStars(this.currentRating);
    this.ratingText.textContent = `${this.currentRating} out of 5`;
  }
  
  updateStars(rating) {
    this.stars.forEach((star, index) => {
      star.textContent = index < rating ? 'star' : 'star_border';
    });
  }
  
  updateARIA() {
    this.element.setAttribute('aria-valuenow', this.currentRating);
    this.element.setAttribute('aria-valuetext', `${this.currentRating} out of 5 stars`);
  }
}

// Initialize rating component
const ratingElement = document.querySelector('.rating');
new AccessibleRating(ratingElement);
</script>

ARIA and JavaScript Integration

Here are some advanced techniques for implementing ARIA attributes:

Managing Focus

Proper focus management is crucial for accessibility, especially in dynamic content like dialogs:

<!-- Focus management for dialogs -->
<script>
class AccessibleDialog {
  constructor(dialogElement) {
    this.dialog = dialogElement;
    this.previousFocus = null;
    this.focusableElements = null;
  }
  
  open() {
    // Store previous focus
    this.previousFocus = document.activeElement;
    
    // Show dialog
    this.dialog.hidden = false;
    this.dialog.setAttribute('aria-modal', 'true');
    
    // Get focusable elements
    this.focusableElements = this.dialog.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    
    // Focus first element
    if (this.focusableElements.length > 0) {
      this.focusableElements[0].focus();
    }
    
    // Trap focus
    this.trapFocus();
  }
  
  close() {
    // Hide dialog
    this.dialog.hidden = true;
    this.dialog.removeAttribute('aria-modal');
    
    // Restore previous focus
    if (this.previousFocus) {
      this.previousFocus.focus();
    }
    
    // Remove focus trap
    this.removeFocusTrap();
  }
  
  trapFocus() {
    this.focusTrapHandler = (e) => {
      if (this.dialog.contains(e.target)) return;
      
      const firstElement = this.focusableElements[0];
      const lastElement = this.focusableElements[this.focusableElements.length - 1];
      
      if (e.shiftKey) {
        if (document.activeElement === firstElement) {
          lastElement.focus();
          e.preventDefault();
        }
      } else {
        if (document.activeElement === lastElement) {
          firstElement.focus();
          e.preventDefault();
        }
      }
    };
    
    document.addEventListener('keydown', this.focusTrapHandler);
  }
  
  removeFocusTrap() {
    document.removeEventListener('keydown', this.focusTrapHandler);
  }
}
</script>

Announcing Dynamic Changes

Use live regions to announce important dynamic changes to screen reader users:

<!-- Announcing content changes -->
<div aria-live="polite" id="announcements"></div>

<script>
class Announcer {
  constructor(element) {
    this.element = element;
  }
  
  announce(message, priority = 'polite') {
    // Create temporary live region if needed
    if (priority === 'assertive') {
      const assertiveRegion = document.createElement('div');
      assertiveRegion.setAttribute('aria-live', 'assertive');
      assertiveRegion.setAttribute('aria-atomic', 'true');
      assertiveRegion.style.position = 'absolute';
      assertiveRegion.style.left = '-10000px';
      assertiveRegion.style.width = '1px';
      assertiveRegion.style.height = '1px';
      assertiveRegion.style.overflow = 'hidden';
      
      document.body.appendChild(assertiveRegion);
      assertiveRegion.textContent = message;
      
      setTimeout(() => {
        document.body.removeChild(assertiveRegion);
      }, 1000);
    } else {
      this.element.textContent = message;
      
      // Clear after announcement
      setTimeout(() => {
        this.element.textContent = '';
      }, 1000);
    }
  }
}

// Usage
const announcer = new Announcer(document.getElementById('announcements'));

// Announce different events
document.getElementById('save-button').addEventListener('click', () => {
  announcer.announce('Document saved successfully');
});

document.getElementById('delete-button').addEventListener('click', () => {
  announcer.announce('Item deleted', 'assertive');
});
</script>

Complete ARIA Implementation Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ARIA Implementation Example</title>
  <meta name="description" content="Complete ARIA implementation with tabs, dialogs, and live regions">
  
  <style>
    /* Basic styling */
    body {
      font-family: Arial, sans-serif;
      line-height: 1.6;
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }
    
    /* Focus styles */
    *:focus {
      outline: 3px solid #0066cc;
      outline-offset: 2px;
    }
    
    /* Tab styles */
    .tabs {
      border: 1px solid #ccc;
      border-radius: 4px;
    }
    
    .tablist {
      display: flex;
      border-bottom: 1px solid #ccc;
    }
    
    .tab {
      background: none;
      border: none;
      padding: 12px 24px;
      cursor: pointer;
      border-bottom: 3px solid transparent;
    }
    
    .tab[aria-selected="true"] {
      border-bottom-color: #0066cc;
      font-weight: bold;
    }
    
    .tab:hover {
      background-color: #f5f5f5;
    }
    
    .tabpanel {
      padding: 20px;
      min-height: 200px;
    }
    
    .tabpanel[hidden] {
      display: none;
    }
    
    /* Dialog styles */
    .dialog-overlay {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, 0.5);
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .dialog {
      background: white;
      padding: 24px;
      border-radius: 8px;
      max-width: 500px;
      width: 100%;
    }
    
    .dialog[hidden] {
      display: none;
    }
    
    /* Live region styles */
    .live-region {
      position: absolute;
      left: -10000px;
      width: 1px;
      height: 1px;
      overflow: hidden;
    }
    
    /* Button styles */
    .btn {
      padding: 8px 16px;
      border: 1px solid #ccc;
      background: white;
      cursor: pointer;
      border-radius: 4px;
    }
    
    .btn:hover {
      background-color: #f5f5f5;
    }
    
    .btn-primary {
      background: #0066cc;
      color: white;
      border-color: #0066cc;
    }
    
    .btn-primary:hover {
      background: #0052a3;
    }
  </style>
</head>
<body>
  <header role="banner">
    <h1>ARIA Implementation Example</h1>
  </header>
  
  <main role="main">
    <section aria-labelledby="tabs-heading">
      <h2 id="tabs-heading">ARIA Tabs Example</h2>
      
      <div class="tabs">
        <div role="tablist" aria-label="Product categories">
          <button class="tab" 
                  role="tab" 
                  aria-selected="true" 
                  aria-controls="electronics-panel" 
                  id="electronics-tab">
            Electronics
          </button>
          <button class="tab" 
                  role="tab" 
                  aria-selected="false" 
                  aria-controls="clothing-panel" 
                  id="clothing-tab">
            Clothing
          </button>
          <button class="tab" 
                  role="tab" 
                  aria-selected="false" 
                  aria-controls="books-panel" 
                  id="books-tab">
            Books
          </button>
        </div>
        
        <div class="tabpanel" 
             role="tabpanel" 
             id="electronics-panel" 
             aria-labelledby="electronics-tab"
             tabindex="0">
          <h3>Electronics</h3>
          <p>Browse our selection of electronic products including smartphones, laptops, and accessories.</p>
          <ul>
            <li>Smartphones starting at $299</li>
            <li>Laptops with latest processors</li>
            <li>Audio equipment and headphones</li>
          </ul>
        </div>
        
        <div class="tabpanel" 
             role="tabpanel" 
             id="clothing-panel" 
             aria-labelledby="clothing-tab"
             tabindex="0"
             hidden>
          <h3>Clothing</h3>
          <p>Discover our fashion collection with styles for every occasion and season.</p>
          <ul>
            <li>Men's and women's apparel</li>
            <li>Seasonal collections</li>
            <li>Accessories and shoes</li>
          </ul>
        </div>
        
        <div class="tabpanel" 
             role="tabpanel" 
             id="books-panel" 
             aria-labelledby="books-tab"
             tabindex="0"
             hidden>
          <h3>Books</h3>
          <p>Explore our vast library of books across all genres and categories.</p>
          <ul>
            <li>Fiction and non-fiction bestsellers</li>
            <li>Educational and technical books</li>
            <li>Children's books and comics</li>
          </ul>
        </div>
      </div>
    </section>
    
    <section aria-labelledby="dialog-heading">
      <h2 id="dialog-heading">ARIA Dialog Example</h2>
      
      <button class="btn btn-primary" id="open-dialog">
        Open Confirmation Dialog
      </button>
      
      <div class="dialog-overlay" id="dialog-overlay" hidden>
        <div class="dialog" 
             role="dialog" 
             aria-modal="true" 
             aria-labelledby="dialog-title"
             id="dialog">
          <h2 id="dialog-title">Confirm Action</h2>
          <p id="dialog-description">
            Are you sure you want to proceed with this action? This cannot be undone.
          </p>
          
          <div style="margin-top: 20px;">
            <button class="btn btn-primary" id="confirm-action">
              Confirm
            </button>
            <button class="btn" id="cancel-action" style="margin-left: 8px;">
              Cancel
            </button>
          </div>
        </div>
      </div>
    </section>
    
    <section aria-labelledby="live-region-heading">
      <h2 id="live-region-heading">Live Region Example</h2>
      
      <button class="btn" id="trigger-message">
        Trigger Status Message
      </button>
      
      <button class="btn" id="trigger-error" style="margin-left: 8px;">
        Trigger Error Message
      </button>
      
      <div style="margin-top: 20px;">
        <strong>Status Messages:</strong>
        <div id="status-display" aria-live="polite" aria-atomic="true">
          <!-- Status messages will appear here -->
        </div>
      </div>
    </section>
  </main>
  
  <!-- Live regions for screen readers -->
  <div class="live-region" aria-live="polite" id="polite-announcer"></div>
  <div class="live-region" aria-live="assertive" id="assertive-announcer"></div>
  
  <script>
    // Tab implementation
    class AccessibleTabs {
      constructor(tablistElement) {
        this.tablist = tablistElement;
        this.tabs = Array.from(tablistElement.querySelectorAll('[role="tab"]'));
        this.panels = this.tabs.map(tab => 
          document.getElementById(tab.getAttribute('aria-controls'))
        );
        
        this.init();
      }
      
      init() {
        this.tabs.forEach((tab, index) => {
          tab.addEventListener('click', () => this.selectTab(index));
          tab.addEventListener('keydown', (e) => this.handleKeydown(e, index));
        });
      }
      
      selectTab(index) {
        // Update tabs
        this.tabs.forEach((tab, i) => {
          tab.setAttribute('aria-selected', i === index);
        });
        
        // Update panels
        this.panels.forEach((panel, i) => {
          panel.hidden = i !== index;
        });
        
        // Focus selected tab
        this.tabs[index].focus();
      }
      
      handleKeydown(e, currentIndex) {
        let newIndex = currentIndex;
        
        switch(e.key) {
          case 'ArrowLeft':
            newIndex = currentIndex > 0 ? currentIndex - 1 : this.tabs.length - 1;
            break;
          case 'ArrowRight':
            newIndex = currentIndex < this.tabs.length - 1 ? currentIndex + 1 : 0;
            break;
          case 'Home':
            newIndex = 0;
            break;
          case 'End':
            newIndex = this.tabs.length - 1;
            break;
          default:
            return;
        }
        
        e.preventDefault();
        this.selectTab(newIndex);
      }
    }
    
    // Dialog implementation
    class AccessibleDialog {
      constructor(dialogElement, triggerElement) {
        this.dialog = dialogElement;
        this.trigger = triggerElement;
        this.overlay = dialogElement.parentElement;
        this.previousFocus = null;
        this.focusableElements = null;
        
        this.init();
      }
      
      init() {
        this.trigger.addEventListener('click', () => this.open());
        
        const closeButtons = this.dialog.querySelectorAll('#cancel-action, #confirm-action');
        closeButtons.forEach(button => {
          button.addEventListener('click', () => this.close());
        });
        
        // Close on escape key
        this.dialog.addEventListener('keydown', (e) => {
          if (e.key === 'Escape') {
            this.close();
          }
        });
      }
      
      open() {
        this.previousFocus = document.activeElement;
        this.overlay.hidden = false;
        
        this.focusableElements = this.dialog.querySelectorAll(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );
        
        if (this.focusableElements.length > 0) {
          this.focusableElements[0].focus();
        }
        
        this.trapFocus();
      }
      
      close() {
        this.overlay.hidden = true;
        
        if (this.previousFocus) {
          this.previousFocus.focus();
        }
        
        this.removeFocusTrap();
      }
      
      trapFocus() {
        this.focusTrapHandler = (e) => {
          if (this.overlay.contains(e.target)) return;
          
          const firstElement = this.focusableElements[0];
          const lastElement = this.focusableElements[this.focusableElements.length - 1];
          
          if (e.shiftKey) {
            if (document.activeElement === firstElement) {
              lastElement.focus();
              e.preventDefault();
            }
          } else {
            if (document.activeElement === lastElement) {
              firstElement.focus();
              e.preventDefault();
            }
          }
        };
        
        document.addEventListener('keydown', this.focusTrapHandler);
      }
      
      removeFocusTrap() {
        document.removeEventListener('keydown', this.focusTrapHandler);
      }
    }
    
    // Live region implementation
    class LiveRegionManager {
      constructor(politeRegion, assertiveRegion, displayRegion) {
        this.politeRegion = politeRegion;
        this.assertiveRegion = assertiveRegion;
        this.displayRegion = displayRegion;
      }
      
      announce(message, priority = 'polite') {
        // Update visual display
        this.displayRegion.textContent = message;
        
        // Announce to screen readers
        if (priority === 'assertive') {
          this.assertiveRegion.textContent = message;
          setTimeout(() => {
            this.assertiveRegion.textContent = '';
          }, 1000);
        } else {
          this.politeRegion.textContent = message;
          setTimeout(() => {
            this.politeRegion.textContent = '';
          }, 1000);
        }
      }
    }
    
    // Initialize components
    document.addEventListener('DOMContentLoaded', () => {
      // Initialize tabs
      const tablist = document.querySelector('[role="tablist"]');
      new AccessibleTabs(tablist);
      
      // Initialize dialog
      const dialog = document.getElementById('dialog');
      const dialogTrigger = document.getElementById('open-dialog');
      new AccessibleDialog(dialog, dialogTrigger);
      
      // Initialize live regions
      const politeRegion = document.getElementById('polite-announcer');
      const assertiveRegion = document.getElementById('assertive-announcer');
      const displayRegion = document.getElementById('status-display');
      const liveRegionManager = new LiveRegionManager(politeRegion, assertiveRegion, displayRegion);
      
      // Live region buttons
      document.getElementById('trigger-message').addEventListener('click', () => {
        liveRegionManager.announce('Status: Operation completed successfully');
      });
      
      document.getElementById('trigger-error').addEventListener('click', () => {
        liveRegionManager.announce('Error: Operation failed. Please try again.', 'assertive');
      });
    });
  </script>
</body>
</html>

๐Ÿงช Quick Quiz

What does aria-hidden='true' do?