Human-Centric Architecture Design
Design systems that prioritize human needs with accessibility-first principles, cognitive load optimization, and inclusive engineering practices.
What is Human-Centric Architecture Design?
Human-centric architecture design puts people at the center of system design decisions. It encompasses accessibility, cognitive ergonomics, inclusive design, and user experience optimization at the architectural level, not just the interface layer.
Modern systems serve diverse global audiences with varying abilities, contexts, and constraints. Human-centric design ensures technology adapts to human diversity rather than forcing humans to adapt to technology limitations.
Interactive Human-Centric Assessment
Accessibility Features
Cognitive Load Features
Inclusive Design Features
Performance Targets
Human-Centric Metrics
Core Human-Centric Principles
1. Universal Accessibility
Design systems usable by people with the widest range of abilities and disabilities.
2. Cognitive Ergonomics
Minimize cognitive load through intuitive information architecture and progressive disclosure.
3. Inclusive by Default
Consider diverse languages, cultures, devices, and economic constraints from the start.
4. Emotional Design
Create positive emotional experiences that build trust and confidence in users.
5. Performance Empathy
Optimize for the constraints of the least privileged users and slowest connections.
6. Privacy-Centric
Respect user agency with transparent data practices and meaningful consent mechanisms.
Production Implementation
Human-Centric Architecture Framework
import React, { createContext, useContext, useEffect, useState } from 'react';
// Human-Centric Context and Providers
interface AccessibilityPreferences {
reducedMotion: boolean;
highContrast: boolean;
largeText: boolean;
screenReaderActive: boolean;
keyboardNavigation: boolean;
voiceControlActive: boolean;
}
interface CognitivePreferences {
complexityLevel: 'minimal' | 'standard' | 'advanced';
progressiveDisclosure: boolean;
contextualHelp: boolean;
autoSave: boolean;
undoRedoAvailable: boolean;
}
interface InclusivePreferences {
language: string;
culturalContext: string;
bandwidth: 'low' | 'medium' | 'high';
deviceCapabilities: {
touchscreen: boolean;
camera: boolean;
microphone: boolean;
gps: boolean;
};
economicContext: 'cost-sensitive' | 'standard' | 'premium';
}
interface HumanCentricContext {
accessibility: AccessibilityPreferences;
cognitive: CognitivePreferences;
inclusive: InclusivePreferences;
updatePreferences: (updates: Partial<any>) => void;
}
const HumanCentricContext = createContext<HumanCentricContext | null>(null);
export const HumanCentricProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [preferences, setPreferences] = useState({
accessibility: {
reducedMotion: false,
highContrast: false,
largeText: false,
screenReaderActive: false,
keyboardNavigation: false,
voiceControlActive: false,
},
cognitive: {
complexityLevel: 'standard' as const,
progressiveDisclosure: true,
contextualHelp: true,
autoSave: true,
undoRedoAvailable: true,
},
inclusive: {
language: 'en',
culturalContext: 'global',
bandwidth: 'medium' as const,
deviceCapabilities: {
touchscreen: true,
camera: true,
microphone: true,
gps: true,
},
economicContext: 'standard' as const,
},
});
// Detect user preferences from system settings
useEffect(() => {
const detectSystemPreferences = () => {
const updates: any = {};
// Detect reduced motion preference
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
updates.accessibility = { ...preferences.accessibility, reducedMotion: true };
}
// Detect high contrast preference
if (window.matchMedia && window.matchMedia('(prefers-contrast: high)').matches) {
updates.accessibility = { ...updates.accessibility, highContrast: true };
}
// Detect screen reader usage
const hasScreenReader = navigator.userAgent.includes('NVDA') ||
navigator.userAgent.includes('JAWS') ||
document.documentElement.hasAttribute('data-at-shortcutkeys');
if (hasScreenReader) {
updates.accessibility = { ...updates.accessibility, screenReaderActive: true };
}
// Detect connection speed
const connection = (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection;
if (connection) {
let bandwidth: 'low' | 'medium' | 'high' = 'medium';
if (connection.effectiveType === '2g' || connection.effectiveType === 'slow-2g') {
bandwidth = 'low';
} else if (connection.effectiveType === '4g') {
bandwidth = 'high';
}
updates.inclusive = { ...preferences.inclusive, bandwidth };
}
// Detect device capabilities
const deviceCapabilities = {
touchscreen: 'ontouchstart' in window,
camera: navigator.mediaDevices && navigator.mediaDevices.getUserMedia !== undefined,
microphone: navigator.mediaDevices && navigator.mediaDevices.getUserMedia !== undefined,
gps: navigator.geolocation !== undefined,
};
updates.inclusive = {
...updates.inclusive,
deviceCapabilities
};
if (Object.keys(updates).length > 0) {
setPreferences(prev => ({ ...prev, ...updates }));
}
};
detectSystemPreferences();
// Listen for preference changes
const mediaQueryList = window.matchMedia('(prefers-reduced-motion: reduce)');
const handleChange = detectSystemPreferences;
mediaQueryList.addEventListener('change', handleChange);
return () => mediaQueryList.removeEventListener('change', handleChange);
}, []);
const updatePreferences = (updates: Partial<typeof preferences>) => {
setPreferences(prev => ({ ...prev, ...updates }));
// Persist preferences
localStorage.setItem('human-centric-preferences', JSON.stringify({ ...preferences, ...updates }));
};
// Load saved preferences
useEffect(() => {
const saved = localStorage.getItem('human-centric-preferences');
if (saved) {
try {
const parsed = JSON.parse(saved);
setPreferences(parsed);
} catch (error) {
console.warn('Failed to parse saved preferences:', error);
}
}
}, []);
return (
<HumanCentricContext.Provider value={{ ...preferences, updatePreferences }}>
{children}
</HumanCentricContext.Provider>
);
};
export const useHumanCentric = () => {
const context = useContext(HumanCentricContext);
if (!context) {
throw new Error('useHumanCentric must be used within HumanCentricProvider');
}
return context;
};
// Accessible Component Framework
export const AccessibleButton: React.FC<{
children: React.ReactNode;
onClick: () => void;
variant?: 'primary' | 'secondary';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
ariaLabel?: string;
ariaDescribedBy?: string;
}> = ({
children,
onClick,
variant = 'primary',
size = 'medium',
disabled = false,
ariaLabel,
ariaDescribedBy
}) => {
const { accessibility, cognitive } = useHumanCentric();
const handleClick = () => {
if (!disabled) {
onClick();
// Provide haptic feedback on supported devices
if ('vibrate' in navigator) {
navigator.vibrate(50);
}
}
};
const baseClasses = `
inline-flex items-center justify-center font-medium rounded-lg
focus:outline-none focus:ring-2 focus:ring-offset-2
transition-all duration-200 ease-in-out
${accessibility.reducedMotion ? 'transition-none' : ''}
${accessibility.highContrast ? 'border-2 border-current' : ''}
${accessibility.largeText ? 'text-lg' : ''}
`;
const sizeClasses = {
small: 'px-3 py-1.5 text-sm min-h-[32px] min-w-[32px]',
medium: 'px-4 py-2 text-base min-h-[44px] min-w-[44px]', // 44px minimum for touch targets
large: 'px-6 py-3 text-lg min-h-[56px] min-w-[56px]'
}[size];
const variantClasses = {
primary: `
bg-blue-600 text-white hover:bg-blue-700
focus:ring-blue-500 disabled:bg-gray-300
${accessibility.highContrast ? 'bg-black text-white hover:bg-gray-800' : ''}
`,
secondary: `
bg-gray-200 text-gray-900 hover:bg-gray-300
focus:ring-gray-500 disabled:bg-gray-100
${accessibility.highContrast ? 'bg-white text-black border-black hover:bg-gray-100' : ''}
`
}[variant];
return (
<button
className={`${baseClasses} ${sizeClasses} ${variantClasses}`}
onClick={handleClick}
disabled={disabled}
aria-label={ariaLabel}
aria-describedby={ariaDescribedBy}
// Ensure keyboard navigation works
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
}}
>
{children}
</button>
);
};
// Cognitive Load Reduction Component
export const ProgressiveDisclosure: React.FC<{
title: string;
children: React.ReactNode;
defaultOpen?: boolean;
level?: 'primary' | 'secondary' | 'tertiary';
}> = ({ title, children, defaultOpen = false, level = 'primary' }) => {
const [isOpen, setIsOpen] = useState(defaultOpen);
const { cognitive, accessibility } = useHumanCentric();
// Auto-expand based on cognitive preferences
useEffect(() => {
if (cognitive.complexityLevel === 'advanced') {
setIsOpen(true);
}
}, [cognitive.complexityLevel]);
const headingLevel = {
primary: 'h2',
secondary: 'h3',
tertiary: 'h4'
}[level];
const HeadingTag = headingLevel as keyof JSX.IntrinsicElements;
return (
<div className="border border-gray-200 dark:border-gray-700 rounded-lg">
<HeadingTag className="m-0">
<button
className={`
w-full px-4 py-3 text-left font-semibold
hover:bg-gray-50 dark:hover:bg-gray-800
focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500
${accessibility.reducedMotion ? '' : 'transition-colors duration-200'}
`}
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
aria-controls={`disclosure-content-${title.replace(/\s+/g, '-').toLowerCase()}`}
>
<span className="flex justify-between items-center">
{title}
<svg
className={`w-5 h-5 transform ${isOpen ? 'rotate-180' : 'rotate-0'} ${
accessibility.reducedMotion ? '' : 'transition-transform duration-200'
}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="m19 9-7 7-7-7" />
</svg>
</span>
</button>
</HeadingTag>
<div
id={`disclosure-content-${title.replace(/\s+/g, '-').toLowerCase()}`}
className={`overflow-hidden ${
accessibility.reducedMotion ? '' : 'transition-all duration-300'
}`}
style={{
maxHeight: isOpen ? '1000px' : '0',
opacity: isOpen ? 1 : 0
}}
>
<div className="px-4 pb-4">
{children}
</div>
</div>
</div>
);
};
// Inclusive Form Component
export const InclusiveForm: React.FC<{
children: React.ReactNode;
onSubmit: (data: FormData) => void;
autoSave?: boolean;
}> = ({ children, onSubmit, autoSave = false }) => {
const { cognitive, accessibility, inclusive } = useHumanCentric();
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
// Auto-save functionality
useEffect(() => {
if (autoSave && cognitive.autoSave && hasUnsavedChanges) {
const timer = setTimeout(() => {
// Implement auto-save logic
console.log('Auto-saving form data...');
setHasUnsavedChanges(false);
}, 2000);
return () => clearTimeout(timer);
}
}, [autoSave, cognitive.autoSave, hasUnsavedChanges]);
const handleFormChange = () => {
setHasUnsavedChanges(true);
};
return (
<form
onSubmit={(e) => {
e.preventDefault();
onSubmit(new FormData(e.currentTarget));
}}
onChange={handleFormChange}
className="space-y-6"
noValidate // We'll handle validation ourselves for better UX
>
{/* Cultural context indicator */}
{inclusive.culturalContext !== 'global' && (
<div className="bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg">
<p className="text-sm text-blue-800 dark:text-blue-200">
Form adapted for {inclusive.culturalContext} context
</p>
</div>
)}
{children}
{/* Auto-save indicator */}
{autoSave && cognitive.autoSave && (
<div className="flex items-center text-sm text-gray-600 dark:text-gray-400">
{hasUnsavedChanges ? (
<span>Saving changes...</span>
) : (
<span>✓ All changes saved</span>
)}
</div>
)}
{/* Keyboard navigation hint */}
{accessibility.keyboardNavigation && (
<div className="text-sm text-gray-500 dark:text-gray-400">
Press Tab to navigate, Enter to submit, Esc to cancel
</div>
)}
</form>
);
};
// Performance-aware Image Component
export const InclusiveImage: React.FC<{
src: string;
alt: string;
width?: number;
height?: number;
priority?: boolean;
}> = ({ src, alt, width, height, priority = false }) => {
const { inclusive, accessibility } = useHumanCentric();
const [imageSrc, setImageSrc] = useState<string>('');
useEffect(() => {
// Adapt image quality based on connection and preferences
let adaptedSrc = src;
if (inclusive.bandwidth === 'low') {
// Use lower quality images for slow connections
adaptedSrc = src.replace(/\.(jpg|jpeg|png)$/i, '_low.$1');
} else if (inclusive.bandwidth === 'high') {
// Use high quality images for fast connections
adaptedSrc = src.replace(/\.(jpg|jpeg|png)$/i, '_high.$1');
}
// Respect data saving preferences
if (inclusive.economicContext === 'cost-sensitive') {
adaptedSrc = src.replace(/\.(jpg|jpeg|png)$/i, '_compressed.$1');
}
setImageSrc(adaptedSrc);
}, [src, inclusive.bandwidth, inclusive.economicContext]);
return (
<img
src={imageSrc || src}
alt={alt}
width={width}
height={height}
loading={priority ? 'eager' : 'lazy'}
decoding="async"
className={`
${accessibility.reducedMotion ? '' : 'transition-opacity duration-300'}
${accessibility.highContrast ? 'filter contrast-125' : ''}
`}
// Provide fallback for failed loads
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = '/images/placeholder.svg';
}}
/>
);
};
// Usage Example Component
export const HumanCentricApp: React.FC = () => {
const { accessibility, cognitive, inclusive, updatePreferences } = useHumanCentric();
return (
<div className="max-w-[1200px] mx-auto px-4 md:px-6 py-8">
<header>
<h1 className="text-3xl font-bold mb-4">Human-Centric Application</h1>
{/* Accessibility preferences panel */}
<ProgressiveDisclosure title="Accessibility Settings" level="secondary">
<div className="grid grid-cols-2 gap-4">
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={accessibility.reducedMotion}
onChange={(e) => updatePreferences({
accessibility: { ...accessibility, reducedMotion: e.target.checked }
})}
/>
<span>Reduce motion</span>
</label>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={accessibility.highContrast}
onChange={(e) => updatePreferences({
accessibility: { ...accessibility, highContrast: e.target.checked }
})}
/>
<span>High contrast</span>
</label>
</div>
</ProgressiveDisclosure>
</header>
<main>
<InclusiveForm
onSubmit={(data) => console.log('Form submitted:', data)}
autoSave={cognitive.autoSave}
>
<div>
<label htmlFor="name" className="block text-sm font-medium mb-2">
Name
</label>
<input
type="text"
id="name"
name="name"
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
aria-describedby="name-help"
/>
{cognitive.contextualHelp && (
<p id="name-help" className="mt-1 text-sm text-gray-600">
Enter your full name as you'd like it to appear
</p>
)}
</div>
<AccessibleButton variant="primary" onClick={() => console.log('Submitted!')}>
Submit Form
</AccessibleButton>
</InclusiveForm>
<ProgressiveDisclosure title="Advanced Options" defaultOpen={cognitive.complexityLevel === 'advanced'}>
<p>Advanced configuration options would appear here...</p>
</ProgressiveDisclosure>
</main>
</div>
);
};
export default HumanCentricApp;
Accessibility Testing Automation
// Automated Accessibility Testing Suite
const { chromium } = require('playwright');
const axe = require('@axe-core/playwright');
class AccessibilityTestSuite {
constructor() {
this.browser = null;
this.context = null;
}
async setup() {
this.browser = await chromium.launch({ headless: false });
this.context = await this.browser.newContext({
// Simulate various user preferences
reducedMotion: 'reduce',
colorScheme: 'dark',
extraHTTPHeaders: {
'User-Agent': 'NVDA/2023.1 (Screen Reader)'
}
});
}
async runComprehensiveAccessibilityTest(url, options = {}) {
const page = await this.context.newPage();
await axe.injectIntoPage(page);
const results = {
url,
timestamp: new Date().toISOString(),
tests: []
};
try {
await page.goto(url);
// Basic axe-core scan
const axeResults = await axe.run(page, {
tags: ['wcag2a', 'wcag2aa', 'wcag21aa', 'best-practice'],
rules: {
'color-contrast': { enabled: true },
'keyboard-navigation': { enabled: true },
'focus-management': { enabled: true },
'aria-usage': { enabled: true }
}
});
results.tests.push({
name: 'WCAG 2.1 AA Compliance',
violations: axeResults.violations,
passes: axeResults.passes,
score: this.calculateAccessibilityScore(axeResults)
});
// Keyboard navigation test
const keyboardResults = await this.testKeyboardNavigation(page);
results.tests.push(keyboardResults);
// Screen reader compatibility test
const screenReaderResults = await this.testScreenReaderCompatibility(page);
results.tests.push(screenReaderResults);
// Cognitive load assessment
const cognitiveResults = await this.testCognitiveLoad(page);
results.tests.push(cognitiveResults);
// Performance accessibility test
const performanceResults = await this.testPerformanceAccessibility(page);
results.tests.push(performanceResults);
// Multi-language support test
if (options.testInternationalization) {
const i18nResults = await this.testInternationalization(page);
results.tests.push(i18nResults);
}
// Mobile accessibility test
const mobileResults = await this.testMobileAccessibility(page);
results.tests.push(mobileResults);
} catch (error) {
results.error = error.message;
} finally {
await page.close();
}
return results;
}
async testKeyboardNavigation(page) {
const results = {
name: 'Keyboard Navigation',
violations: [],
passes: [],
score: 0
};
try {
// Test tab navigation
const focusableElements = await page.$$('[tabindex]:not([tabindex="-1"]), button, input, select, textarea, a[href]');
let tabCount = 0;
let currentElement = null;
for (let i = 0; i < Math.min(focusableElements.length, 20); i++) {
await page.keyboard.press('Tab');
tabCount++;
const focusedElement = await page.evaluate(() => {
const element = document.activeElement;
return {
tagName: element.tagName,
role: element.getAttribute('role'),
ariaLabel: element.getAttribute('aria-label'),
visible: element.offsetParent !== null
};
});
if (!focusedElement.visible) {
results.violations.push({
description: 'Tab landed on invisible element',
element: focusedElement,
tabIndex: tabCount
});
} else {
results.passes.push({
description: 'Tab navigation to visible element',
element: focusedElement
});
}
}
// Test escape key functionality
await page.keyboard.press('Escape');
const escapeHandled = await page.evaluate(() => {
// Check if any modals or overlays were closed
return document.querySelectorAll('[role="dialog"]:not([aria-hidden="true"])').length === 0;
});
if (escapeHandled) {
results.passes.push({ description: 'Escape key properly handled' });
}
// Test Enter and Space key activation
const buttons = await page.$$('button:not([disabled])');
if (buttons.length > 0) {
await buttons[0].focus();
await page.keyboard.press('Enter');
// Check if button action was triggered
results.passes.push({ description: 'Enter key activates buttons' });
await page.keyboard.press('Space');
results.passes.push({ description: 'Space key activates buttons' });
}
results.score = (results.passes.length / (results.passes.length + results.violations.length)) * 100;
} catch (error) {
results.violations.push({
description: 'Keyboard navigation test failed',
error: error.message
});
}
return results;
}
async testScreenReaderCompatibility(page) {
const results = {
name: 'Screen Reader Compatibility',
violations: [],
passes: [],
score: 0
};
try {
// Test ARIA landmarks
const landmarks = await page.$$('[role="main"], [role="navigation"], [role="banner"], [role="contentinfo"], [role="complementary"]');
if (landmarks.length > 0) {
results.passes.push({
description: `Found ${landmarks.length} ARIA landmarks`,
count: landmarks.length
});
} else {
results.violations.push({
description: 'No ARIA landmarks found',
impact: 'serious'
});
}
// Test heading structure
const headings = await page.$$('h1, h2, h3, h4, h5, h6, [role="heading"]');
const headingLevels = await Promise.all(
headings.map(h => h.evaluate(el => ({
level: el.tagName.charAt(1) || el.getAttribute('aria-level'),
text: el.textContent?.trim()
})))
);
// Check for proper heading hierarchy
let previousLevel = 0;
for (const heading of headingLevels) {
const currentLevel = parseInt(heading.level);
if (currentLevel > previousLevel + 1) {
results.violations.push({
description: `Heading level skip: jumped from h${previousLevel} to h${currentLevel}`,
text: heading.text
});
} else {
results.passes.push({
description: `Proper heading hierarchy: h${currentLevel}`,
text: heading.text
});
}
previousLevel = currentLevel;
}
// Test alt text for images
const images = await page.$$('img');
for (const img of images) {
const altText = await img.getAttribute('alt');
const ariaLabel = await img.getAttribute('aria-label');
const role = await img.getAttribute('role');
if (role === 'presentation' || altText === '') {
results.passes.push({ description: 'Decorative image properly marked' });
} else if (altText || ariaLabel) {
results.passes.push({ description: 'Image has accessible text' });
} else {
results.violations.push({
description: 'Image missing alt text',
src: await img.getAttribute('src')
});
}
}
// Test form labels
const inputs = await page.$$('input, select, textarea');
for (const input of inputs) {
const id = await input.getAttribute('id');
const ariaLabel = await input.getAttribute('aria-label');
const ariaLabelledBy = await input.getAttribute('aria-labelledby');
let hasLabel = false;
if (id) {
const label = await page.$(`label[for="${id}"]`);
if (label) hasLabel = true;
}
if (ariaLabel || ariaLabelledBy) hasLabel = true;
if (hasLabel) {
results.passes.push({ description: 'Form input has accessible label' });
} else {
results.violations.push({
description: 'Form input missing accessible label',
type: await input.getAttribute('type') || 'input'
});
}
}
results.score = (results.passes.length / (results.passes.length + results.violations.length)) * 100;
} catch (error) {
results.violations.push({
description: 'Screen reader compatibility test failed',
error: error.message
});
}
return results;
}
async testCognitiveLoad(page) {
const results = {
name: 'Cognitive Load Assessment',
violations: [],
passes: [],
score: 0,
metrics: {}
};
try {
// Count interactive elements
const interactiveElements = await page.$$('button, input, select, textarea, a[href], [tabindex]');
results.metrics.interactiveElementCount = interactiveElements.length;
if (interactiveElements.length > 50) {
results.violations.push({
description: 'High cognitive load: too many interactive elements',
count: interactiveElements.length
});
} else {
results.passes.push({
description: 'Manageable number of interactive elements',
count: interactiveElements.length
});
}
// Analyze information density
const textContent = await page.evaluate(() => document.body.textContent);
const wordCount = textContent.split(/\s+/).length;
results.metrics.wordCount = wordCount;
const viewportHeight = await page.evaluate(() => window.innerHeight);
const contentHeight = await page.evaluate(() => document.body.scrollHeight);
const contentDensity = wordCount / (contentHeight / viewportHeight);
results.metrics.contentDensity = contentDensity;
if (contentDensity > 100) {
results.violations.push({
description: 'High information density may cause cognitive overload',
density: contentDensity
});
} else {
results.passes.push({
description: 'Appropriate information density',
density: contentDensity
});
}
// Check for progressive disclosure
const collapsibleElements = await page.$$('[aria-expanded]');
if (collapsibleElements.length > 0) {
results.passes.push({
description: 'Progressive disclosure implemented',
count: collapsibleElements.length
});
}
// Check for contextual help
const helpElements = await page.$$('[aria-describedby], .help-text, .tooltip');
if (helpElements.length > 0) {
results.passes.push({
description: 'Contextual help available',
count: helpElements.length
});
}
results.score = (results.passes.length / (results.passes.length + results.violations.length)) * 100;
} catch (error) {
results.violations.push({
description: 'Cognitive load assessment failed',
error: error.message
});
}
return results;
}
async testPerformanceAccessibility(page) {
const results = {
name: 'Performance Accessibility',
violations: [],
passes: [],
score: 0,
metrics: {}
};
try {
// Measure page load performance
const performanceMetrics = await page.evaluate(() => {
const perfData = performance.getEntriesByType('navigation')[0];
return {
loadTime: perfData.loadEventEnd - perfData.loadEventStart,
domContentLoaded: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
firstContentfulPaint: performance.getEntriesByName('first-contentful-paint')[0]?.startTime || 0
};
});
results.metrics = performanceMetrics;
// Check load time accessibility
if (performanceMetrics.loadTime < 3000) {
results.passes.push({
description: 'Fast page load time',
loadTime: performanceMetrics.loadTime
});
} else {
results.violations.push({
description: 'Slow page load may impact users with cognitive disabilities',
loadTime: performanceMetrics.loadTime
});
}
// Check for loading indicators
const loadingIndicators = await page.$$('[role="progressbar"], .loading, .spinner');
if (loadingIndicators.length > 0) {
results.passes.push({
description: 'Loading indicators present',
count: loadingIndicators.length
});
}
// Test reduced motion preference
await page.emulateMedia({ reducedMotion: 'reduce' });
const animatedElements = await page.$$('[class*="animate"], [class*="transition"]');
// Check if animations are properly disabled
for (const element of animatedElements) {
const computedStyle = await element.evaluate(el => {
const style = getComputedStyle(el);
return {
animationDuration: style.animationDuration,
transitionDuration: style.transitionDuration
};
});
if (computedStyle.animationDuration === '0s' || computedStyle.transitionDuration === '0s') {
results.passes.push({ description: 'Animations respect reduced motion preference' });
} else {
results.violations.push({
description: 'Animations do not respect reduced motion preference',
element: 'animated element'
});
}
}
results.score = (results.passes.length / (results.passes.length + results.violations.length)) * 100;
} catch (error) {
results.violations.push({
description: 'Performance accessibility test failed',
error: error.message
});
}
return results;
}
calculateAccessibilityScore(axeResults) {
const totalRules = axeResults.passes.length + axeResults.violations.length + axeResults.incomplete.length;
if (totalRules === 0) return 0;
const passWeight = 1;
const violationWeight = -2;
const incompleteWeight = -0.5;
const score = (
(axeResults.passes.length * passWeight) +
(axeResults.violations.length * violationWeight) +
(axeResults.incomplete.length * incompleteWeight)
) / totalRules;
return Math.max(0, Math.min(100, (score + 2) / 3 * 100));
}
async teardown() {
if (this.browser) {
await this.browser.close();
}
}
}
// Usage example
async function runAccessibilityTests() {
const suite = new AccessibilityTestSuite();
await suite.setup();
const results = await suite.runComprehensiveAccessibilityTest('https://example.com', {
testInternationalization: true
});
console.log('Accessibility Test Results:', JSON.stringify(results, null, 2));
await suite.teardown();
}
module.exports = { AccessibilityTestSuite };
Real-World Examples
UK Government (GOV.UK)
Accessibility-First Design System
GOV.UK built their design system with accessibility as the primary constraint, not an afterthought. Every component is tested with screen readers, keyboard navigation, and cognitive load assessment. Their approach demonstrates how accessibility-first design improves usability for everyone.
Microsoft
Inclusive Design at Scale
Microsoft's Inclusive Design methodology recognizes that exclusion is the source of innovation. Their human-centric approach led to features like live captions, immersive reader, and adaptive controllers that benefit millions of users beyond their original target audience.
Wikipedia
Global Inclusive Architecture
Wikipedia's architecture prioritizes global accessibility with 300+ languages, offline reading capabilities, and bandwidth-adaptive content delivery. Their human-centric design ensures knowledge accessibility regardless of device, connection, or economic constraints.