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>
Password too short
I accept the terms of use

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>
Format: name@example.com
Minimum 8 characters with numbers and letters
Notification options
Receive important updates by email
Receive urgent alerts by SMS
ARIA attributes used:
  • aria-label: Provides an accessible label
  • aria-describedby: Associates a detailed description
  • aria-required: Indicates a required field
  • aria-invalid: Signals an invalid field
  • aria-labelledby: References an existing label
  • role="alert": Immediately announces errors
  • aria-hidden: Hides decorative elements
  • aria-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

What is web accessibility?+
Why is it important?+
How to get started?+
<!-- 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:

✅ Focusable elements

Link
Custom button (div)

❌ Non-focusable elements

Simple text (div)
Inline text (span)

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

Good example

Explanation: A dropdown menu must support keyboard navigation, close with Escape, and maintain focus appropriately.

Accessible image gallery

Bad example

Good example

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 ↑Email Role Status
Alice Martinalice@example.comAdminActive
Bob Dupontbob@example.comUserInactive
Charlie Leroycharlie@example.comModeratorActive
Diana Moreaudiana@example.comUserSuspended

Good example

Alice Martinalice@example.comAdminActive
Bob Dupontbob@example.comUserInactive
Charlie Leroycharlie@example.comModeratorActive
Diana Moreaudiana@example.comUserSuspended
Click on column headers to sort the table. Use Tab to navigate between sort buttons.

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)

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

Buttons without description

These buttons have no text indication:

Problems:
  • Screen readers only announce "button"
  • Impossible to know the action without seeing the icon
  • Emojis can be misinterpreted
  • No tooltip on hover

Good example

Accessible buttons

Several approaches to make icons accessible:

1. With aria-label
2. With visible text
3. With visually hidden text
Best practices:
  • Always provide alternative text (aria-label, hidden text, or visible text)
  • Use aria-hidden="true" on decorative icons
  • Add tooltips with the title attribute
  • Prefer visible text when space allows

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

0 result(s) available

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 reading
  • role="alert" + aria-live="assertive": For critical errors, interrupts immediately
  • aria-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.