aria-ease
v6.12.0
Published
Accessibility infrastructure for the entire frontend engineering lifecycle. Build accessible patterns, run automated audits, verify component interactions, and gate deployments — all in one system.
Maintainers
Readme
Aria-Ease
Accessibility infrastructure for the entire frontend engineering lifecycle.
Stop treating accessibility as an afterthought. Aria-Ease engineers accessibility integrity into every phase of frontend development lifecycle — from local development to production monitoring — so accessibility violations never reach your users.
🏗️ Infrastructure, Not Just Utilities
Aria-Ease isn't a utility library. It's an accessibility infrastructure that integrates into every phase of your frontend engineering lifecycle:
| Phase | Feature | Status | Impact | | ------------------ | --------------------------------------------- | ------------ | --------------------------------------------- | | 🔧 Development | Component utilities for accessible patterns | ✅ Available | Build it right from the start | | ⚡ Linting | ESLint rules to enforce accessible coding | 🚧 Roadmap | Catch mistakes as you type | | 🔍 Pre-Deploy | Axe-core powered static accessibility audit | ✅ Available | Verify before it ships | | 🧪 Testing | WAI-ARIA APG contract testing with Playwright | ✅ Available | Fast, determinic component accessibility test | | 🚀 CI/CD | Accessibility as deployment gatekeeper | ✅ Available | Block inaccessible code from production | | 📊 Production | Real user signal monitoring and replay | 🚧 Roadmap | Understand how users actually interact | | 📈 Insights | Dashboard for reporting and analytics | 🚧 Roadmap | Visualize accessibility health |
🎯 The Complete Lifecycle
From Development to Production
Traditional approach: Build features → Manual testing → Find accessibility issues → Fix them → Manual testing again → Ship (maybe)
Aria-Ease approach: Build with accessible baseline utilities → Automated audits catch issues → Contract tests verify consistent baseline component behaviors → CI/CD gates deployment → Ship with confidence
What Makes This Different?
1. Component Utilities (Available Now)
Reusable accessible interaction patterns based on Aria-Ease's baseline interpretation of WAI-ARIA APG guidance. Not the only valid implementation, but a proven, consistent place to start. Tree-shakable, framework-agnostic, production-ready.
// Instead of 50+ lines managing ARIA attributes and keyboard events...
import { makeMenuAccessible } from "aria-ease/menu";
const menu = makeMenuAccessible({
menuId: "user-menu",
triggerId: "profile-button",
menuItemsClass: "menu-item",
}); // Arrow keys, Escape, focus management — all handled2. Static Audit (Available Now)
Axe-core powered CLI that scans your entire site and generates comprehensive reports. Run it locally or in CI/CD.
npx aria-ease audit --url https://yoursite.com3. Contract Testing (Available Now)
This is the game-changer. Encode a deterministic, testable interpretation of WAI-ARIA APG guidance into JSON "contracts" using Aria-Ease DSL API, and validate your contract against your component using Aria-Ease's Playwright runner with isolated test-harness architecture. Run it locally or in CI/CD.
Teams and experts can enforce their own standards and maintain reusability and consistency.
The result? Component interaction testing that feels closer to unit testing than manual QA.
npx aria-ease test
# ✓ 26 assertions in ~2 seconds
# ✓ 26 assertions in ~1 second in CIWhy this matters: Before, verifying a combobox meant testing every interaction manually. Now, Aria-Ease automates the repeatable, deterministic aspects of testing a combobox: keyboard interaction, ARIA state updates, visibility, and semantic roles.
4. CI/CD Integration (Available Now)
Turn accessibility into a deployment invariant. Add audit and test commands to your pipeline — if they fail, deployment is blocked.
# .github/workflows/deploy.yml
jobs:
accessibility-gate:
runs-on: ubuntu-latest
steps:
- run: npm run build
- run: npx aria-ease audit # Static checks
- run: npx aria-ease test # Interaction tests
# Only deploy if both pass ☝️Real example from our docs site: Push to branch → Accessibility checks run → Green check mark → Deploys to Firebase. Red X → Deploy blocked.
No one has any excuse to ship inaccessible code anymore.
5. Linting (Roadmap)
ESLint rules that enforce accessible coding patterns as you type. Catch issues before they compile.
6. Production Monitoring (Roadmap)
Real user signal monitoring, interaction replay, and analytics. Understand how assistive technology users actually experience your app.
7. Insights Dashboard (Roadmap)
Visualize accessibility health across your entire application. Track progress, identify patterns, generate reports.
✨ Features
- 🎯 Tree-shakable - Import only what you need (1.4KB - 3.7KB per component)
- ♿ WCAG Compliant - Follows WAI-ARIA best practices
- ⌨️ Keyboard Interaction - Full keyboard support out of the box
- 🧪 Contract Testing - Built-in baseline accessibility testing framework
- 🎭 Framework Agnostic - Works with React, Vue, vanilla JS, etc.
- 🔍 CLI Audit Tool - Automated accessibility testing for your sites
- 📦 TypeScript Support - Full type definitions included
📦 Installation
npm i aria-ease
# or
yarn add aria-ease
# or
pnpm add aria-ease🚀 Quick Start
Automated Accessibility Audits (CLI)
Run axe-core powered automated accessibility audits on your website with one command:
npx aria-ease audit --url https://yoursite.comThis generates comprehensive reports in JSON, CSV, and HTML formats showing all accessibility violations detected by axe-core.
Create a config file for multiple pages and custom settings:
// ariaease.config.js (or .mjs, .cjs, .json, .ts)
export default {
audit: {
urls: [
"https://yoursite.com",
"https://yoursite.com/about",
"https://yoursite.com/contact",
],
output: {
format: "html", // 'json' | 'csv' | 'html' | 'all'
out: "./accessibility-reports",
},
},
test: {
strictness: "balanced", // 'minimal' | 'balanced' | 'strict' | 'paranoid'
},
};Then run:
npx aria-ease auditSupported config formats:
ariaease.config.js(ES modules)ariaease.config.mjs(ES modules explicit)ariaease.config.cjs(CommonJS)ariaease.config.json(JSON)ariaease.config.ts(TypeScript - experimental)
The CLI will automatically find and load your config file, with validation to catch errors early.
Contract DSL Build Workflow
You can author custom component contracts as readable DSL files and compile them with a built-in CLI command.
- Create one or more
*.contract.mjsfiles that export a contract built withcontract("component.name", ...). - Configure contract sources in
ariaease.config.js. - Run
npx aria-ease build contracts. - Run
npx aria-ease test.
Example config with multiple contract sources:
export default {
test: {
strictness: "balanced",
components: [
{
name: "combobox",
path: "./tests/external-contracts/combobox.listbox.contract.json",
strategyPath: "./tests/external-strategies/CustomComboboxStrategy.js",
},
],
},
contracts: [
{
src: "./tests/external-contracts/**/*.contract.mjs",
// optional: out: "./tests/external-contracts/generated"
},
{
src: "./tests/client-a/**/*.contract.mjs",
out: "./tests/client-a/generated",
},
],
};Example package.json scripts:
{
"scripts": {
"build:contracts": "npx aria-ease build contracts",
"test:a11y": "npx aria-ease build contracts && npx aria-ease test"
}
}During build, Aria-Ease validates:
- contract schema shape
- selector references in static/dynamic targets
- relationship references (
aria-reference,contains)
At runtime, relationship invariants are executed and reported alongside static assertions and dynamic tests.
Perfect for CI/CD pipelines to catch accessibility issues before production!
🏅 Show Your Accessibility Commitment
After auditing your project, show the world you care about accessibility! Add a badge to your README:
For projects audited with aria-ease:
[](https://github.com/aria-ease/aria-ease)For projects with tested components:
[](https://github.com/aria-ease/aria-ease)For projects using both audits and component tests:
[](https://github.com/aria-ease/aria-ease)Why add the badge?
- ✅ Shows your commitment to accessibility
- 🔍 Helps us discover projects using aria-ease
- 🌟 Promotes accessibility best practices in the community
- 📈 Free marketing for both your project and aria-ease!
Tip: The CLI will prompt you to add the appropriate badge automatically after running audits or tests!
📚 Component API
🍔 Menu (Dropdowns)
Creates accessible menus with aria attribute management, focus trapping, and keyboard interaction. Works for dropdowns that toggles display with interactive items.
Features:
- Arrow key navigation
- Escape key closes menu and restores focus
- Focus trap within menu
- Submenu support
import * as Menu from "aria-ease/menu";
// React Example
useEffect(() => {
menuRef.current = Menu.makeMenuAccessible({
menuId: "menu-div",
menuItemsClass: "profile-menu-items",
triggerId: "display-button",
});
return () => menuRef.current.cleanup(); // Clean up on unmount
}, []);
// Programmatically control
menuRef.current.openMenu(); // Open the menu
menuRef.current.closeMenu(); // Close the menu
menuRef.current.refresh(); // Refresh the cache after dynamically adding/removing a menu item
// Vanilla JS Example
const menu = Menu.makeMenuAccessible({
menuId: "dropdown-menu",
menuItemsClass: "menu-item",
triggerId: "menu-button",
});
// Programmatically control
menu.openMenu();
menu.closeMenu();
// If you dynamically add/remove menu items, refresh the cache
menu.refresh();Required HTML structure:
<button id="menu-button" aria-label="Home example menu">Menu</button>
<div id="dropdown-menu" style="display: none;">
<a href="#" class="menu-item">Item 1</a>
<a href="#" class="menu-item">Item 2</a>
<button class="menu-item">Item 3</button>
</div>🎮 Live Demo
- Menu Component - Menu dropdown with keyboard interaction
🍔 Combobox (Listbox)
Creates accessible listbox combobox with aria attribute management, focus trapping, and keyboard interaction.
Features:
- Arrow key navigation
- Escape key closes listbox
- ARIA attribute management (aria-expanded, aria-activedescendant, aria-controls, roles)
- Focus management with aria-activedescendant pattern
import * as Combobox from "aria-ease/combobox";
// React Example
useEffect(() => {
comboboxRef.current = Combobox.makeComboboxAccessible({
comboboxInputId: "search-input",
listBoxId: "suggestions-list",
listBoxItemsClass: "suggestion-item",
});
return () => {
if (comboboxRef.current) {
comboboxRef.current.cleanup();
}
};
}, []);
// Programmatically control
comboboxRef.current.openListbox(); // Open the listbox
comboboxRef.current.refresh(); // Refresh the cache after dynamically adding/removing a listbox option item
// Vanilla JS Example
const combobox = Combobox.makeComboboxAccessible({
comboboxInputId: "fruit",
comboboxButtonId: "show-list-button",
listBoxId: "fruits-listbox",
listBoxItemsClass: "list-option",
callback: {
onSelect: (option) => {
input.value = option.textContent;
// Show all options after selection
options.forEach((opt) => (opt.hidden = false));
},
onOpenChange: (isOpen) => {
console.log("Listbox is", isOpen ? "open" : "closed");
},
},
});
// Programmatically control
combobox.openListbox();
combobox.closeListbox();
// If you dynamically add/remove listbox option items, refresh the cache
combobox.refresh();Required HTML structure:
<div id="combo-wrapper">
<label for="fruit">Select a fruit</label>
<div class="input-wrapper">
<input type="text" id="fruit" placeholder="Search fruits..." />
<button id="show-list-button" tabindex="-1">▼</button>
</div>
<ul id="fruits-listbox">
<li id="apple" class="list-option">Apple</li>
<li id="mango" class="list-option">Mango</li>
<li id="orange" class="list-option">Orange</li>
<li id="banana" class="list-option">Banana</li>
</ul>
</div>🎮 Live Demo
- Combobox Component - Listbox combobox with keyboard interaction
🪗 Accordion
Creates accessible accordions with keyboard interaction and automatic state management.
Features:
- Arrow key navigation between triggers
- Automatic ARIA attribute management
- Single or multiple panel expansion
- Enter/Space to toggle panels
- Home/End key support
import { makeAccordionAccessible } from "aria-ease/accordion";
// React Example
useEffect(() => {
const accordion = makeAccordionAccessible({
accordionId: "accordion-container",
triggersClass: "accordion-trigger",
panelsClass: "accordion-panel",
allowMultipleOpen: false, // Only one panel open at a time (default)
});
return () => accordion.cleanup();
}, []);
// Programmatic control
accordion.expandItem(0); // Expand first panel
accordion.collapseItem(1); // Collapse second panel
accordion.toggleItem(2); // Toggle third panelHTML structure:
<div id="accordion-container">
<button class="accordion-trigger">Section 1</button>
<div class="accordion-panel">Content 1</div>
<button class="accordion-trigger">Section 2</button>
<div class="accordion-panel">Content 2</div>
<button class="accordion-trigger">Section 3</button>
<div class="accordion-panel">Content 3</div>
</div>import { makeAccordionAccessible } from "aria-ease/accordion";
const accordionStates = [{ display: true }, { display: false }];
makeAccordionAccessible({
accordionId: "faq-div",
triggersClass: "dropdown-button",
panelsClass: "accordion-panel",
allowMultipleOpen: false, // Only one panel open at a time
});✅ Checkbox
Creates accessible checkbox groups with keyboard interaction and state management.
Features:
- Space to toggle
- Independent state tracking
- Query checked states
import { makeCheckboxAccessible } from "aria-ease/checkbox";
// React Example
useEffect(() => {
const checkboxGroup = makeCheckboxAccessible({
checkboxGroupId: "checkbox-group",
checkboxesClass: "custom-checkbox",
});
return () => checkboxGroup.cleanup();
}, []);
// Programmatic control
checkboxGroup.toggleCheckbox(0); // Toggle first checkbox
checkboxGroup.setCheckboxState(1, true); // Check second checkbox
const states = checkboxGroup.getCheckedStates(); // [true, false, true]
const indices = checkboxGroup.getCheckedIndices(); // [0, 2]HTML structure:
<div id="checkbox-group">
<div class="custom-checkbox" aria-label="Option 1"></div>
<div class="custom-checkbox" aria-label="Option 2"></div>
<div class="custom-checkbox" aria-label="Option 3"></div>
</div>import { makeCheckboxAccessible } from "aria-ease/checkbox";
makeCheckboxAccessible({
checkboxGroupId: "checkbox-div",
checkboxesClass: "course-checkbox",
});🔘 Radio Button
Creates accessible radio groups with keyboard interaction and automatic selection management.
Features:
- Arrow key navigation (all directions)
- Automatic unchecking of other radios
- Space to select
- Single selection enforcement
import { makeRadioAccessible } from "aria-ease/radio";
// React Example
useEffect(() => {
const radioGroup = makeRadioAccessible({
radioGroupId: "radio-group",
radiosClass: "custom-radio",
defaultSelectedIndex: 0, // Initially selected (optional)
});
return () => radioGroup.cleanup();
}, []);
// Programmatic control
radioGroup.selectRadio(2); // Select third radio
const selected = radioGroup.getSelectedIndex(); // Get current selectionHTML structure:
<div id="radio-group">
<div class="custom-radio" aria-label="Option 1"></div>
<div class="custom-radio" aria-label="Option 2"></div>
<div class="custom-radio" aria-label="Option 3"></div>
</div>import { makeRadioAccessible } from "aria-ease/radio";
makeRadioAccessible({
radioGroupId: "radio-div",
radiosClass: "radio",
defaultSelectedIndex: 0, // Optional: which radio is selected initially
});🔀 Toggle Button
Creates accessible toggle buttons with keyboard interactions and state management.
Features:
- Enter/Space to toggle
- Single toggle or toggle groups
- Query pressed states
import { makeToggleAccessible } from "aria-ease/toggle";
// Single toggle button
const toggle = makeToggleAccessible({
toggleId: "mute-button",
isSingleToggle: true,
});
// Toggle button group
const toggleGroup = makeToggleAccessible({
toggleId: "toolbar",
togglesClass: "toggle-btn",
isSingleToggle: false,
});
// Programmatic control
toggle.toggleButton(0); // Toggle the button
toggle.setPressed(0, true); // Set pressed state
const states = toggleGroup.getPressedStates(); // [false, true, false]
const indices = toggleGroup.getPressedIndices(); // [1]
// Cleanup
toggle.cleanup();HTML structure:
<!-- Single toggle -->
<button id="mute-button">Mute</button>
<!-- Toggle group -->
<div id="toolbar">
<button class="toggle-btn">Bold</button>
<button class="toggle-btn">Italic</button>
<button class="toggle-btn">Underline</button>
</div>import { updateToggleAriaAttribute } from "aria-ease/toggle";
const toggleStates = [{ pressed: false }];
updateToggleAriaAttribute("toggle-container", "toggle-btn", toggleStates, 0);🧱 Block (Generic Focusable Groups)
Makes groups of elements keyboard navigable with arrow keys. Perfect for custom controls, toolbars, or any grouped interactive elements.
import { makeBlockAccessible } from "aria-ease/block";
const blockInstance = makeBlockAccessible({
blockId: "toolbar",
blockItemsClass: "tool-button",
});
// Clean up when done
blockInstance.current.cleanup();🧪 Testing Your Components
Aria-Ease includes a built-in testing framework for automated accessibility validation:
import { describe, test, afterAll } from "vitest";
import { testUiComponent, cleanupTests } from "aria-ease/test";
import { render } from "@testing-library/react";
import ShopifyUserMenu from "../src/components/menus/ShopifyUserMenu";
afterAll(async () => {
await cleanupTests();
});
describe("Shopify User Menu Accessibility Test", () => {
test("renders Shopify user menu without accessibility violation(s)", async () => {
await testUiComponent(
"menu",
null,
"http://localhost:5173/test-harness?component=menu",
); // For full component interaction test. Uses Playwright to test interaction and behaviors
});
});
describe("Shopify User Menu Accessibility Test", () => {
test("renders Shopify user menu without accessibility violation(s)", async () => {
const { container } = render(<ShopifyUserMenu />);
await testUiComponent("menu", container, null); // For fast limited static tests. Doesn't test for interaction and behaviors
});
});Strictness Modes
Aria-Ease supports strictness policies to define how APG specs are enforced.
minimalrequired-> errorrecommended-> ignoreoptional-> ignore
balanced(default)required-> errorrecommended-> warningoptional-> ignore
strictrequired-> errorrecommended-> erroroptional-> warning
paranoidrequired-> errorrecommended-> erroroptional-> error
Configure strictness globally in ariaease.config.*:
export default {
test: {
strictness: "balanced",
},
};Or define strictness per component (recommended when components have different maturity levels):
export default {
test: {
strictness: "balanced", // fallback
components: [
{
name: "menu",
strictness: "strict",
},
{
name: "accordion",
strictness: "minimal",
},
],
},
};path is optional and not required for strictness resolution.
Or override per test call:
await testUiComponent(
"menu",
null,
"http://localhost:5173/test-harness?component=menu",
{
strictness: "strict",
},
);📦 Bundle Size
Aria-Ease is designed to be lightweight and tree-shakable:
| Import | Size (ESM) |
| ---------------------------- | --------------------- |
| aria-ease/accordion | ~6.5KB |
| aria-ease/checkbox | ~6.0KB |
| aria-ease/radio | ~5.5KB |
| aria-ease/toggle | ~6.0KB |
| aria-ease/menu | ~6.7KB |
| aria-ease/block | ~1.7KB |
| aria-ease/combobox | ~8.1KB |
| Full bundle (all components) | ~459KB (uncompressed) |
💡 Tip: Always import individual components for optimal bundle size:
// ✅ Good - only imports menu code (~6.7KB)
import { makeMenuAccessible } from "aria-ease/menu";
//or
import * as Block from "aria-ease/block";
// ❌ Avoid - imports everything (~459KB)
import { makeMenuAccessible } from "aria-ease";⚠️ Important: React StrictMode
If using React StrictMode, be aware it intentionally calls effects twice in development. This can cause issues with imperative DOM manipulation. Either:
- Remove
<React.StrictMode>in development, or - Use proper cleanup functions:
useEffect(() => {
menuRef.current = Menu.makeMenuAccessible({...});
return () => menuRef.current.cleanup(); // Prevents double-initialization
}, []);🎨 Focus Styling
Aria-Ease handles ARIA attributes and keyboard interaction, but you must provide visible focus styles:
:focus {
outline: 2px solid rgba(0, 91, 211, 1);
outline-offset: 2px;
}
/* Or custom styles */
.menu-item:focus {
background: #e3f2fd;
box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.3);
}Without visible focus indicators, keyboard users cannot tell which element is active.
🌐 Browser Support
Aria-Ease supports all modern browsers:
| Browser | Minimum Version | | ------- | --------------- | | Chrome | Last 2 versions | | Firefox | Last 2 versions | | Safari | Last 2 versions | | Edge | Last 2 versions |
Not supported: Internet Explorer 11 and below
Requirements:
- ES6+ support
querySelectorandquerySelectorAlladdEventListenerandremoveEventListener- Modern event handling (
KeyboardEvent,FocusEvent)
For older browser support, use a polyfill service or transpile with appropriate targets.
🚀 CI/CD Integration: Accessibility as a Deployment Gatekeeper
The game-changer: Turn accessibility into a deployment invariant. Code that fails accessibility checks cannot reach production.
Why This Matters
Until now, accessibility testing often happened manually, late in the cycle, or not at all. Even with good intentions, inaccessible code slips through when testing is manual and optional.
Aria-Ease changes the equation:
- ✅ Automated = no human bottleneck
- ✅ Fast = ~6 seconds for 90 accessibility interaction assertions
- ✅ Deterministic = same results every time
- ✅ Blocking = deploy fails if tests fail
Real example: The aria-ease docs site has a pipeline that:
- Runs
aria-ease auditon every push - Runs
aria-ease testfor component interaction verification - Only deploys to Firebase if both pass
- Blocks deployment if either fails
Result: Green check ✓ = live docs. Red X ✗ = deploy blocked. No exceptions.
GitHub Actions Example
Here's a complete workflow that gates deployment on accessibility:
name: Accessibility Gate + Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
accessibility-checks:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm install
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Build application
run: npm run build
- name: Start dev server
run: |
npm run dev &
npx wait-on http://localhost:5173 -t 30000
- name: Run accessibility audit
run: npx aria-ease audit
# ⬆️ If audit fails, workflow stops here
- name: Run component contract tests
run: |
set -o pipefail
npm run test 2>&1 | tee component-contract-test-output.txt
# ⬆️ If interaction tests fail, workflow stops here
- name: Upload audit reports
if: failure()
uses: actions/upload-artifact@v4
with:
name: accessibility-reports
path: accessibility-reports/
retention-days: 30
- name: Upload component contract tests report
if: failure()
uses: actions/upload-artifact@v4
with:
name: component-contract-test-output
path: component-contract-test-output.txt
retention-days: 30
deploy:
needs: accessibility-checks # ⬅️ This is the key
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm install
- name: Build for production
run: npm run build
- name: Deploy to Firebase
uses: w9jds/firebase-action@master
with:
args: deploy --only hosting
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
# Deployment only happens if accessibility-checks job passes ✅Configuration for CI/CD
Create ariaease.config.js in your project root:
export default {
audit: {
urls: [
"http://localhost:5173", // Homepage
"http://localhost:5173/docs", // Docs
"http://localhost:5173/examples", // Examples
],
output: {
format: "all", // Generate JSON, CSV, and HTML reports
out: "./accessibility-reports",
},
},
contracts: [
{
src: "./tests/external-contracts/**/*.contract.mjs",
},
],
};Add to package.json:
{
"scripts": {
"audit": "npx aria-ease audit -f html",
"test:a11y": "npx aria-ease build contracts && npx aria-ease test",
"ci": "npm run audit && npm run test:a11y"
}
}Other CI Platforms
GitLab CI:
accessibility:
stage: test
script:
- npm install
- npm run build
- npm run dev &
- npx wait-on http://localhost:5173
- npx aria-ease audit
- npx aria-ease test
artifacts:
paths:
- accessibility-reports/
when: always
deploy:
stage: deploy
needs: [accessibility] # Gates deployment
script:
- npm run deploy
only:
- mainCircleCI:
version: 2.1
jobs:
accessibility:
docker:
- image: mcr.microsoft.com/playwright:v1.40.0-focal
steps:
- checkout
- run: npm install
- run: npm run build
- run: npm run dev &
- run: npx wait-on http://localhost:5173
- run: npx aria-ease audit
- run: npx aria-ease test
- store_artifacts:
path: accessibility-reports
deploy:
docker:
- image: node:20
steps:
- checkout
- run: npm install
- run: npm run deploy
workflows:
build-test-deploy:
jobs:
- accessibility
- deploy:
requires:
- accessibility # Gates deployment
filters:
branches:
only: mainPerformance & Speed
One of the biggest blockers to adding accessibility testing to CI/CD is speed. Nobody wants pipelines that take 30 minutes.
Aria-Ease contract testing is fast:
npx aria-ease test
# ✓ 26 combobox interaction assertions in ~1 seconds in CI
# ✓ 90 accessibility interaction assertions in ~6 seconds in CI
Why so fast?
- Custom Playwright runner optimized for component testing
- Isolated test-harness architecture (no full app bootstrapping)
- Deterministic JSON contracts (no flaky selectors)
- Runs headless in CI
Compare to manual testing: Manually verifying keyboard interactions for a combobox listbox (arrow keys, typing, Enter, Escape, Home/End, etc.) across browsers, verifying WAI-ARIA roles, states and properties = 20-30 minutes. Aria-Ease = ~2 seconds.
The Moment of Truth
The first time you see that green check mark in your CI/CD pipeline—knowing that both static accessibility violations AND interaction behavior have been verified automatically—that's when it clicks.
No one has any excuse to ship inaccessible code anymore.
You've shifted accessibility left (into development), automated the verification, and made it a deployment gatekeeper.
📖 More Resources
🤝 Contributing
We welcome contributions! See our contribution guidelines to get started.
📄 License
ISC License - see LICENSE file for details.
Created by Isaac Victor
