ARIA
Enrich the semantics of complex components to make them accessible
What is ARIA?
ARIA (Accessible Rich Internet Applications) is a W3C specification that allows improving the accessibility of rich web applications and dynamic components.
🏷️ Roles
Define what an element is (button, navigation, alert...)
role="button"📊 States
Describe the current state of an element (checked, expanded, disabled...)
aria-expanded="true"🔧 Properties
Provide additional information (label, describedby, controls...)
aria-label="Main menu"⚠️ ARIA Golden Rule
Do not use ARIA if you can use native HTML
Forms with ARIA attributes
Bad example
Form without ARIA
❌ Inaccessible form without appropriate ARIA attributes:
<!-- Bad: no label, no description -->
<input type="text" placeholder="Your email">
<!-- Bad: icon not accessible -->
<button>
<span class="icon">🔍</span>
</button>
<!-- Bad: error not associated, no autocomplete -->
<input type="password">
<div class="error">Password too short</div>Good example
Form with ARIA
✅ Accessible form with appropriate ARIA attributes:
<!-- Good: aria-label for field without visible label -->
<input
type="text"
aria-label="Email address (required)"
aria-describedby="email-desc"
aria-required="true">
<span id="email-desc">Format: name@example.com</span>
<!-- Good: button with aria-label -->
<button aria-label="Submit form">
<span aria-hidden="true">🔍</span>
</button>
<!-- Good: error associated with aria-describedby -->
<input
type="password"
aria-label="Password (8 characters minimum)"
aria-invalid="true"
aria-describedby="pwd-error"
autocomplete="new-password">
<div id="pwd-error" role="alert">
Password must contain at least 8 characters
</div>ARIA attributes used:
aria-label: Provides an accessible labelaria-describedby: Associates a detailed descriptionaria-required: Indicates a required fieldaria-invalid: Signals an invalid fieldaria-labelledby: References an existing labelrole="alert": Immediately announces errorsaria-hidden: Hides decorative elementsaria-live: Announces dynamic changes
Explanation: ARIA attributes enrich forms by providing labels, descriptions and states to assistive technologies.
Accessible accordion
Bad example
Without ARIA attributes
<!-- Bad: No ARIA attributes, no keyboard navigation -->
<div class="accordion-header" @click="toggle">
<span>Title</span>
<span>+</span>
</div>
<div v-if="open">
Content
</div>Good example
With proper ARIA
<!-- Good: Complete ARIA with keyboard navigation -->
<button
id="accordion-button-1"
aria-expanded="false"
aria-controls="panel-1"
@click="toggle"
@keydown.enter="toggle"
>
Title
</button>
<div
id="panel-1"
role="region"
aria-labelledby="accordion-button-1"
:hidden="!open"
>
Content
</div>Explanation: An accordion must use appropriate ARIA attributes, support keyboard navigation and announce state changes.
Tabindex and keyboard navigation
The tabindex attribute controls whether an element can receive keyboard focus and in what order.
✅ tabindex="0"
Element is focusable in the natural DOM order
tabindex="0"<!-- Good: Interactive element focusable -->
<div role="button" aria-label="Custom action" tabindex="0">
Custom button
</div>⚠️ tabindex="-1"
Focusable by JavaScript but not by Tab
tabindex="-1"<!-- Good: For programmatic focus -->
<div role="alert" aria-live="assertive" tabindex="-1">
Error message
</div>❌ tabindex > 0
Changes navigation order (avoid!)
tabindex="5"<!-- Bad: Breaks natural tab order -->
<button tabindex="3">Third</button>
<button tabindex="1">First</button>
<button tabindex="2">Second</button>📋 Tabindex best practices
- Use
tabindex="0"to make custom interactive elements focusable - Use
tabindex="-1"for programmatic focus (modals, alerts) - NEVER use positive values - respect DOM order
- Native interactive elements (
<button>,<a>,<input>) don't need tabindex
🎯 Interactive examples
Test keyboard navigation (Tab/Shift+Tab) with these elements:
❌ Non-focusable elements
Paragraph
💡 Testing tips
- Use Tab to navigate forward
- Use Shift + Tab to navigate backward
- Use Space or Enter to activate elements
- Observe the focus indicator (blue outline)
- Note which elements can or cannot receive focus
Accessible dropdown menu
Bad example
Menu without ARIA
<!-- Bad: No menu role, no focus trap -->
<div @click="open = !open">
Actions ▼
</div>
<div v-if="open">
<div @click="action">Edit</div>
<div @click="action">Delete</div>
</div>Good example
Menu with ARIA
<!-- Good: ARIA attributes and focus management -->
<button
aria-haspopup="true"
aria-expanded="false"
aria-controls="menu"
@keydown.escape="close"
>
Actions
</button>
<div
id="menu"
role="menu"
v-if="open"
>
<button role="menuitem">Edit</button>
<button role="menuitem">Delete</button>
</div>Explanation: A dropdown menu must support keyboard navigation, close with Escape, and maintain focus appropriately.
Accessible image gallery
Bad example
Accessible image gallery
<!-- ❌ Bad: gallery not keyboard accessible -->
<div class="thumbnails">
<img v-for="image in images"
:src="image.thumb"
@click="selectImage(index)"
:class="{ active: selected === index }" />
</div>
<div class="main-image">
<img :src="images[selected].full" />
</div>Good example
Accessible image gallery
Front view of the 16-inch MacBook Pro with the screen closed, showing the sleek aluminum design.
<!-- ✅ Good: accessible gallery with keyboard navigation -->
<div role="tablist" aria-label="Product images">
<button v-for="(image, index) in images"
:id="`tab-${index}`"
role="tab"
:aria-selected="selected === index"
:aria-controls="`panel-${index}`"
:tabindex="selected === index ? 0 : -1"
@click="selectImage(index)"
@keydown="handleKeydown($event, index)">
<img :src="image.thumb" :alt="`${image.alt} - Miniature`" />
</button>
</div>
<div :id="`panel-${selected}`"
role="tabpanel"
:aria-labelledby="`tab-${selected}`">
<img :src="images[selected].full" :alt="images[selected].alt" />
<p>{{ images[selected].description }}</p>
</div>
<!-- Bonnes pratiques :
- Pattern tablist/tab/tabpanel ARIA
- Navigation avec flèches, Home, End
- Focus management avec tabindex
- Descriptions détaillées pour chaque image -->Explanation: An image gallery must allow keyboard navigation and provide contextual descriptions for each image. The ARIA tablist/tab/tabpanel pattern is perfect for this use case.
Accessible sortable table
Bad example
| Name ↑ | Role | Status | |
|---|---|---|---|
| Alice Martin | alice@example.com | Admin | Active |
| Bob Dupont | bob@example.com | User | Inactive |
| Charlie Leroy | charlie@example.com | Moderator | Active |
| Diana Moreau | diana@example.com | User | Suspended |
Good example
| Alice Martin | alice@example.com | Admin | Active |
|---|---|---|---|
| Bob Dupont | bob@example.com | User | Inactive |
| Charlie Leroy | charlie@example.com | Moderator | Active |
| Diana Moreau | diana@example.com | User | Suspended |
Explanation: A sortable table must indicate the current sort state, allow keyboard navigation, and announce changes to screen readers.
Dynamic aria-live regions
Bad example
Without aria-live
Changes are not announced
<!-- Mauvais : pas d'attribut aria-live -->
<div class="status-banner">
<p></p>
</div>Counter: 0
Problems:
- Changes are not announced
- Screen reader users don't know content has changed
- No indication of message importance
Good example
With appropriate aria-live
Changes are announced according to their priority
<!-- Bon : avec aria-live="polite" pour informations -->
<div
class="status-banner"
aria-live="polite"
aria-atomic="true"
role="status"
>
<p>Information : Données mises à jour</p>
</div>
<!-- Bon : avec aria-live="assertive" pour alertes -->
<div
class="alert-banner"
aria-live="assertive"
aria-atomic="true"
role="alert"
>
<p>Attention : Action requise immédiatement</p>
</div>Polite zone (information)
Assertive zone (alerts)
Advantages:
- Important messages interrupt with aria-live="assertive"
- Informational messages wait with aria-live="polite"
- aria-atomic="true" announces entire region content
- Appropriate roles (status, alert) provide more context
Explanation: Aria-live regions allow announcing content changes to screen readers. 'polite' waits for the current announcement to finish, 'assertive' interrupts immediately.
Accessible icon buttons
Bad example
Good example
Explanation: Buttons that contain only icons must have an aria-label or visually hidden text to be understood by screen readers.
Combobox / Search with suggestions
Bad example
Without ARIA or keyboard navigation
Good example
With ARIA and keyboard navigation
Explanation: A search with suggestions (combobox) must be keyboard navigable, announce results and allow selection.
Toast notifications (Live Regions)
Bad example
Without aria-live
❌ Notifications are not announced to screen readers
<!-- Bad: not announced to screen readers -->
<div v-if="showToast" class="toast">
Votre demande a été enregistrée
</div>Good example
With appropriate aria-live
✅ Notifications are properly announced
<!-- Good: info notification (polite) -->
<div
v-if="showToast"
role="status"
aria-live="polite"
aria-atomic="true"
class="toast"
>
<p>Votre demande a été enregistrée</p>
<button @click="dismiss" aria-label="Fermer notification">✕</button>
</div>
<!-- Good: error alert (assertive) -->
<div
v-if="showError"
role="alert"
aria-live="assertive"
aria-atomic="true"
class="toast error"
>
<p>Erreur : action impossible à exécuter</p>
<button @click="dismiss" aria-label="Fermer alerte">✕</button>
</div>Differences:
role="status"+aria-live="polite": For informational notifications, doesn't interrupt current readingrole="alert"+aria-live="assertive": For critical errors, interrupts immediatelyaria-atomic="true": Announces entire notification content
Explanation: Dynamic notifications must be announced to screen readers without stealing focus. Use role='status' for info and role='alert' for errors.