Skip to main content
Web DevelopmentSoftware Engineering

Building Accessible Web Applications: Beyond ARIA Labels

Accessibility isn't an afterthought. Learn to build inclusive web apps with semantic HTML, keyboard navigation, and ARIA done right.

Mbeah Essilfie

Mbeah Essilfie

June 9, 2026 at 06:03 AM

9 min read 2002 views
Building Accessible Web Applications: Beyond ARIA Labels

Accessibility Is Not a Checkbox

1 in 4 adults in the US has a disability. Accessibility isn't just ethical — it's practical. Accessible apps work better for everyone: keyboard power users, people with slow connections, users of screen readers, and people in bright sunlight.

Start with Semantic HTML

<!-- ❌ Div soup -->
<div class="header">
  <div class="nav">
    <div class="link" onclick="navigate()">Home</div>
  </div>
</div>

<!-- ✅ Semantic HTML — free accessibility -->
<header>
  <nav aria-label="Main navigation">
    <a href="/">Home</a>
  </nav>
</header>

Semantic elements provide meaning to assistive technology without extra attributes.

Keyboard Navigation

Every interactive element must be keyboard-accessible:

<template>
  <div
    role="tablist"
    @keydown.left="selectPrev"
    @keydown.right="selectNext"
    @keydown.home="selectFirst"
    @keydown.end="selectLast"
  >
    <button
      v-for="(tab, i) in tabs"
      :key="tab.id"
      role="tab"
      :tabindex="i === activeIndex ? 0 : -1"
      :aria-selected="i === activeIndex"
      @click="select(i)"
    >
      {{ tab.label }}
    </button>
  </div>
</template>
Test your app by unplugging your mouse for 30 minutes. If you can't complete core workflows with keyboard alone, you have accessibility issues.

Focus Management

When content changes dynamically, manage focus:

// After opening a modal
function openModal() {
  isOpen.value = true
  nextTick(() => {
    modalRef.value?.focus()
  })
}

// After deleting an item from a list
function deleteItem(index: number) {
  items.value.splice(index, 1)
  nextTick(() => {
    // Focus the next item, or previous if it was the last
    const nextIndex = Math.min(index, items.value.length - 1)
    itemRefs.value[nextIndex]?.focus()
  })
}

Color and Contrast

  • Minimum 4.5:1 contrast ratio for normal text
  • Minimum 3:1 for large text (18px+ or 14px+ bold)
  • Never use color as the only indicator
<!-- ❌ Color only -->
<span class="text-red-500">Error</span>

<!-- ✅ Color + icon + text -->
<span class="text-red-500 flex items-center gap-1">
  <IconAlertCircle aria-hidden="true" />
  Error: Email is required
</span>

ARIA: Use Sparingly

The first rule of ARIA: don't use ARIA if native HTML does the job.

<!-- ❌ Over-engineered -->
<div role="button" tabindex="0" aria-pressed="false" onclick="...">

<!-- ✅ Just use a button -->
<button>Click me</button>

Testing Tools

  • axe DevTools — Browser extension for automated scanning
  • VoiceOver (Mac) / NVDA (Windows) — Screen reader testing
  • Lighthouse — Accessibility audits in Chrome DevTools

Build for everyone from the start. It's always cheaper than retrofitting.

JavaScriptVueCSS
Mbeah Essilfie

Written by Mbeah Essilfie

Fullstack Software Developer

Read next

The Complete Guide to Vue 3 Composables
12 min read

The Complete Guide to Vue 3 Composables

Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.

Mbeah EssilfieMbeah Essilfie
984