Repository navigation
Accessibility Features
VAST is built with accessibility in mind, following WCAG 2.1 AA guidelines to ensure the theme is usable by everyone.
Proper use of HTML5 semantic elements ensures assistive technologies can understand page structure.
-
<header>- Site header with navigation -
<nav>- Navigation menus (main menu, footer menu, pagination) -
<main>- Primary page content -
<footer>- Site footer with secondary links -
<aside>- Sidebar content (filters, related content) -
<article>- Blog posts and standalone content -
<section>- Thematic grouping of content
Proper heading structure (h1, h2, h3) throughout all templates:
- One h1 per page - Page title or main heading
- Logical h2-h6 nesting - No skipped heading levels
- Descriptive headings - Clearly describe section content
-
<ul>/<ol>for all list-based content -
<nav>wrapper for all navigation menus - Proper link markup with descriptive text (no "click here")
Interactive elements have descriptive labels:
<button aria-label="Add {{ product.title }} to cart">
{% render 'icon-cart' %}
</button>
<a href="/cart" aria-label="Shopping cart with {{ cart.item_count }} items">
{% render 'icon-cart' %}
<span>{{ cart.item_count }}</span>
</a>Dynamic content has appropriate roles:
-
role="listbox"- Search results dropdown -
role="navigation"- Menu systems -
role="dialog"- Modal dialogs -
role="alert"- Error messages and notifications -
role="status"- Loading states and status updates
Dynamic elements communicate their state:
-
aria-expanded="true|false"- Collapsible content (FAQ, mobile menu) -
aria-hidden="true|false"- Visibility state (modals, drawers) -
aria-current="page"- Current page in navigation -
aria-live="polite"- Dynamic cart updates -
aria-busy="true"- Loading states during async operations
<button
aria-expanded="false"
aria-controls="faq-{{ forloop.index }}"
@click="open = !open">
{{ block.settings.question }}
</button>
<div
id="faq-{{ forloop.index }}"
x-show="open"
x-collapse
role="region"
aria-labelledby="faq-button-{{ forloop.index }}">
{{ block.settings.answer }}
</div>Full keyboard support for all interactive elements.
Skip link visible on focus allows keyboard users to bypass navigation:
<a href="#main-content" class="skip-to-content">
Skip to content
</a>
<main id="main-content" tabindex="-1">
<!-- Page content -->
</main>Styled to appear only when focused:
.skip-to-content {
position: absolute;
top: -100px;
left: 0;
}
.skip-to-content:focus {
top: 0;
}Full keyboard support for modals:
- Tab key - Navigate between focusable elements
- Shift + Tab - Navigate backwards
- Escape key - Close modal
-
Focus trapping - Tab cycles within modal (via
@alpinejs/focus) - Focus restoration - Returns focus to trigger element on close
Implemented with Alpine.js Focus plugin:
<div
x-show="open"
x-trap.noscroll="open"
role="dialog"
aria-modal="true"
@keydown.escape="open = false">
<!-- Modal content -->
</div>All interactive elements are keyboard accessible:
- Buttons - Activated with Space or Enter
- Links - Activated with Enter
- Form controls - Standard keyboard navigation
-
Custom controls - Proper
tabindexand keyboard event handlers
All text meets WCAG AA contrast requirements:
Brand Colors (WCAG Compliant):
-
blue-600on white: 4.76:1 (AA ✓) -
blue-700on white: 6.1:1 (AAA ✓) -
White on
blue-600: 4.76:1 (AA ✓) -
White on
blue-700: 6.1:1 (AAA ✓)
Button States:
-
Default:
bg-blue-600 text-white(4.76:1) -
Hover:
hover:bg-blue-700(6.1:1, provides visible feedback) -
Focus:
focus:ring-2 focus:ring-blue-400(visible focus ring)
Visible focus indicators on all interactive elements:
button:focus-visible,
a:focus-visible {
outline: 2px solid theme('colors.blue.500');
outline-offset: 2px;
}Custom focus ring utilities:
class="focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2"Accessible form validation with error messages:
<label for="email">Email Address</label>
<input
type="email"
id="email"
name="email"
required
aria-describedby="email-error"
aria-invalid="{{ form.errors contains 'email' }}">
{% if form.errors contains 'email' %}
<span id="email-error" role="alert" class="text-red-600">
{{ form.errors.translated_fields['email'] | capitalize }}
{{ form.errors.messages['email'] }}
</span>
{% endif %}Visual feedback for async operations:
<button
type="submit"
:disabled="loading"
:aria-busy="loading">
<span x-show="!loading">Add to Cart</span>
<span x-show="loading">
{% render 'icon-spinner' %}
<span class="sr-only">Adding to cart...</span>
</span>
</button>Content visible to screen readers but hidden visually:
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}Usage:
<button>
{% render 'icon-cart' %}
<span class="sr-only">Shopping cart</span>
</button>Dynamic content updates announced to screen readers:
<div aria-live="polite" aria-atomic="true">
<span class="sr-only">
Cart updated: {{ cart.item_count }} items, total {{ cart.total_price | money }}
</span>
</div>All images have descriptive alt text:
<img
src="{{ product.featured_image | img_url: '800x' }}"
alt="{{ product.featured_image.alt | default: product.title }}"
loading="lazy">Decorative images use empty alt:
<img src="{{ 'decorative-pattern.svg' | asset_url }}" alt="" role="presentation">All form inputs have associated labels:
<label for="customer-email">Email Address</label>
<input
type="email"
id="customer-email"
name="customer[email]"
autocomplete="email"
required>Required fields clearly marked:
<label for="name">
Full Name
<abbr title="required" aria-label="required">*</abbr>
</label>
<input type="text" id="name" name="name" required>Proper autocomplete values for common fields:
<input type="email" autocomplete="email">
<input type="text" autocomplete="given-name">
<input type="text" autocomplete="family-name">
<input type="tel" autocomplete="tel">
<input type="text" autocomplete="address-line1">- axe DevTools - Browser extension for accessibility testing
- WAVE - Web accessibility evaluation tool
- Lighthouse - Built into Chrome DevTools
Keyboard Navigation:
- Unplug your mouse
- Navigate site using only Tab, Shift+Tab, Enter, Space, Escape
- Ensure all interactive elements are reachable and usable
Screen Reader Testing:
- macOS: VoiceOver (Cmd + F5)
- Windows: NVDA (free) or JAWS
- Mobile: TalkBack (Android) or VoiceOver (iOS)
Color Contrast:
- Use browser DevTools color picker
- Check contrast ratios meet WCAG AA (4.5:1 for normal text)
- SEO Features - SEO optimization documentation
- Features Reference - Detailed feature documentation
- Development Guide - Development workflow and tools