WCAG Guidelines
Comprehensive accessibility standards covering perceivable, operable, understandable, robust.
Accessibility First: Design for keyboard navigation and screen readers from the start, not as an afterthought. Accessible design benefits everyone, not just users with disabilities.
Many websites are designed exclusively for mouse/touch interaction, creating barriers for:
Common accessibility failures:
These failures affect people who depend on keyboard or switch input, as well as people using a keyboard because of injury, context, or preference. BBC’s own focus guidance explicitly notes that “keyboard users” are not a single disability group and that keyboard access is an alternative to fine pointer control (BBC GEL: Focus).
The evidence for this case study is BBC’s published Global Experience Language (GEL) technical guidance. GEL describes framework-independent recommendations for BBC developers, contractors, and suppliers, with an explicit emphasis on the BBC Accessibility Guidelines (BBC GEL technical documentation). It documents intended patterns; it is not an audit of every BBC page or app.
1. Semantic controls before ARIA reconstruction
BBC’s focus guide contrasts a scripted div role="button" with a native button, noting
that the latter is focusable and responds to Enter and Space without added JavaScript
(BBC GEL: Focus).
2. Visible, resilient focus treatment
The published GEL examples use a solid outline and describe focus styles as functional, not decorative (BBC GEL: Focus).
3. Logical focus order
tabindex valuesThese constraints are stated directly in the focus guidance (BBC GEL: Focus order).
4. Bypass controls for repeated or dense interfaces
BBC’s routing guide requires the main element to be reachable with a skip link, while the
Share tools component demonstrates a focused “Skip sharing” link and focusable end target
(BBC GEL: Routing,
BBC GEL: Share tools).
5. Component semantics expose structure and state
nav landmarkaria-expandedaria-currentThe GEL Site menu documents all three decisions and explains what common screen readers announce (BBC GEL: Site menu).
6. Restore focus after transient UI closes
This behavior is specified in the GEL Action dialog pattern (BBC GEL: Action dialogs).
The following are expected effects of the documented patterns, not measured outcomes for all BBC services.
BBC’s public GEL material establishes the intended keyboard patterns and supplies reference implementations. It does not publish the before/after task-completion, navigation-time, screen-reader-error, or satisfaction figures previously shown on this page. Those figures have therefore been removed.
To measure an implementation of this pattern, report:
| Measure | Evidence to collect |
|---|---|
| Keyboard task completion | Moderated or unmoderated task test, with product surface and sample stated |
| Focus-order defects | Audit findings by component and viewport |
| Keyboard traps | Reproducible defect count, including browser and assistive technology |
| Time to bypass repeated content | Task timing with and without the bypass control |
| User confidence | Survey instrument, sample, and uncertainty—not an unattributed score |
WCAG Guidelines
Comprehensive accessibility standards covering perceivable, operable, understandable, robust.
Screen Readers
Designing for screen readers with semantic HTML and ARIA.
Keyboard Navigation
Keyboard interaction patterns for common UI components.
Ergonomics
Touch target sizes and motor control considerations.
When you use the Human Standards MCP server, these rules enforce keyboard accessibility:
wcag-keyboard-accessible
// Triggered when interactive elements aren't keyboard accessible{ severity: 'error', rule: 'wcag-keyboard-accessible', message: 'Interactive element must be keyboard accessible', recommendation: 'Use semantic HTML (<button>, <a>) or add tabindex="0" + keyboard handlers', reference: '/code-design-tokens/aria-keyboard-patterns/'}wcag-focus-visible
// Checks for visible focus indicators{ severity: 'error', rule: 'wcag-focus-visible', message: 'Focus indicator must be visible (do not use outline: none)', recommendation: 'Provide 3:1 contrast focus indicator on all interactive elements', reference: '/accessibility/wcag-guidelines/#operable'}wcag-skip-links
// Validates skip navigation links{ severity: 'warning', rule: 'wcag-skip-links', message: 'Pages with repeated navigation should have skip links', recommendation: 'Add "Skip to main content" link as first focusable element', reference: '/code-design-tokens/aria-keyboard-patterns/'}wcag-aria-labels
// Checks for accessible names{ severity: 'error', rule: 'wcag-aria-labels', message: 'Interactive element lacks accessible name', recommendation: 'Add aria-label, aria-labelledby, or visible text content', reference: '/accessibility/assistive-technologies/#screen-readers'}// Get relevant heuristics for accessibilityconst userControl = await mcp.callTool('get_heuristic', { id: 'H3' });// Returns: User control and freedom - undo, cancel, escape routes
const consistency = await mcp.callTool('get_heuristic', { id: 'H4' });// Returns: Consistency and standards - follow platform conventions
// Search for accessibility-specific documentationconst accessibilityDocs = await mcp.callTool('search_standards', { query: 'keyboard navigation' });// Returns: Focus management, skip links, ARIA patterns
const wcagDocs = await mcp.callTool('search_standards', { query: 'WCAG accessibility' });// Returns: WCAG guidelines, screen readers, assistive technologies
// Example results inform implementation:// - H3 (User Control): Escape key closes modal, clear exit paths// - H4 (Consistency): Follow platform keyboard conventions// - Keyboard docs: Focus trapping, tab order, visible focus indicators// - WCAG docs: aria-modal, role="dialog", screen reader announcementsimport { useEffect, useRef, useState } from 'react';
// Skip link component based on the documented patternexport function SkipLink({ targetId }: { targetId: string }) { const handleSkip = (e: React.MouseEvent) => { e.preventDefault(); const target = document.getElementById(targetId); if (target) { target.focus(); target.scrollIntoView(); } };
return ( <a href={`#${targetId}`} className="skip-link" onClick={handleSkip} > Skip to main content </a> );}
// Accessible modal with focus managementexport function AccessibleModal({ isOpen, onClose, title, children}: { isOpen: boolean; onClose: () => void; title: string; children: React.ReactNode;}) { const modalRef = useRef<HTMLDivElement>(null); const previousFocusRef = useRef<HTMLElement | null>(null); const [focusTrapActive, setFocusTrapActive] = useState(false);
// Store element that triggered modal useEffect(() => { if (isOpen) { previousFocusRef.current = document.activeElement as HTMLElement; setFocusTrapActive(true); } }, [isOpen]);
// Move focus to modal when opened useEffect(() => { if (isOpen && modalRef.current) { // Focus first focusable element in modal const firstFocusable = modalRef.current.querySelector<HTMLElement>( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); firstFocusable?.focus(); } }, [isOpen]);
// Return focus when closed useEffect(() => { if (!isOpen && previousFocusRef.current) { previousFocusRef.current.focus(); setFocusTrapActive(false); } }, [isOpen]);
// Keyboard handlers useEffect(() => { if (!isOpen) return;
const handleKeyDown = (e: KeyboardEvent) => { // Escape to close if (e.key === 'Escape') { onClose(); return; }
// Focus trap: Tab cycling if (e.key === 'Tab' && modalRef.current) { const focusableElements = modalRef.current.querySelectorAll<HTMLElement>( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' );
if (focusableElements.length === 0) return;
const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1];
// Shift+Tab on first element → go to last if (e.shiftKey && document.activeElement === firstElement) { e.preventDefault(); lastElement.focus(); } // Tab on last element → go to first else if (!e.shiftKey && document.activeElement === lastElement) { e.preventDefault(); firstElement.focus(); } } };
document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose]);
if (!isOpen) return null;
return ( <> {/* Backdrop */} <div className="modal-backdrop" onClick={onClose} aria-hidden="true" />
{/* Modal */} <div ref={modalRef} className="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title" > <div className="modal-header"> <h2 id="modal-title">{title}</h2> <button onClick={onClose} className="modal-close" aria-label="Close dialog" > ✕ </button> </div>
<div className="modal-content"> {children} </div>
<div className="modal-footer"> <button onClick={onClose} className="button-secondary"> Cancel </button> <button className="button-primary"> Confirm </button> </div> </div> </> );}
// Accessible button with proper focus indicatorexport function AccessibleButton({ children, onClick, variant = 'primary', disabled = false, ariaLabel}: { children: React.ReactNode; onClick?: () => void; variant?: 'primary' | 'secondary'; disabled?: boolean; ariaLabel?: string;}) { return ( <button onClick={onClick} disabled={disabled} className={`accessible-button ${variant}`} aria-label={ariaLabel} // Proper button semantics (automatically keyboard accessible) > {children} </button> );}
// Keyboard-navigable menu based on the documented patternexport function KeyboardNavigableMenu({ items }: { items: Array<{ label: string; href: string }>}) { const [activeIndex, setActiveIndex] = useState(-1); const menuRef = useRef<HTMLUListElement>(null);
const handleKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'ArrowDown': e.preventDefault(); setActiveIndex(prev => (prev + 1) % items.length); break; case 'ArrowUp': e.preventDefault(); setActiveIndex(prev => (prev - 1 + items.length) % items.length); break; case 'Home': e.preventDefault(); setActiveIndex(0); break; case 'End': e.preventDefault(); setActiveIndex(items.length - 1); break; } };
// Focus active item when index changes useEffect(() => { if (activeIndex >= 0 && menuRef.current) { const activeLink = menuRef.current.children[activeIndex]?.querySelector('a'); activeLink?.focus(); } }, [activeIndex]);
return ( <nav aria-label="Main navigation"> <ul ref={menuRef} className="keyboard-menu" role="menu" onKeyDown={handleKeyDown} > {items.map((item, index) => ( <li key={index} role="none"> <a href={item.href} role="menuitem" className={index === activeIndex ? 'active' : ''} > {item.label} </a> </li> ))} </ul> </nav> );}/* Skip link (visible on focus) */.skip-link { position: absolute; top: -40px; left: 0; z-index: 10000;
/* Ergonomics: Large touch target */ padding: 12px 24px; min-height: 48px;
/* Accessibility: High contrast (WCAG AAA) */ background: #FFEB3B; /* Yellow */ color: #000; /* Black: 15.4:1 contrast */
font-weight: 700; text-decoration: none; border-radius: 0 0 4px 0;
/* Hidden until focused */ transition: top 0.2s;}
.skip-link:focus { /* Appears on first Tab press */ top: 0; outline: 3px solid #000; outline-offset: 2px;}
/* Example high-contrast focus indicator */:focus { /* NEVER use outline: none without replacement! */ outline: 3px solid #FFEB3B; /* BBC yellow */ outline-offset: 2px;}
/* Ensure focus is visible on all backgrounds */:focus-visible { outline: 3px solid #FFEB3B; outline-offset: 2px;}
/* Dark mode: Adjust focus color for visibility */@media (prefers-color-scheme: dark) { :focus, :focus-visible { outline-color: #FFD700; /* Brighter yellow for dark backgrounds */ }}
/* Modal with proper focus management */.modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); z-index: 1000;}
.modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1001;
max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto;
background: #fff; border-radius: 8px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);}
.modal-header { display: flex; justify-content: space-between; align-items: center; padding: 24px; border-bottom: 1px solid #E0E0E0;}
.modal-close { /* Ergonomics: 48×48px touch target */ min-width: 48px; min-height: 48px; padding: 12px;
background: transparent; border: none; font-size: 24px; color: #757575; cursor: pointer; border-radius: 4px; transition: all 0.2s;}
.modal-close:hover { background: #F5F5F5; color: #212121;}
.modal-close:focus { /* Visible focus indicator */ outline: 3px solid #FFEB3B; outline-offset: 2px;}
.modal-content { padding: 24px;}
.modal-footer { display: flex; gap: 12px; justify-content: flex-end; padding: 24px; border-top: 1px solid #E0E0E0;}
/* Accessible buttons */.accessible-button { /* Ergonomics: 48×48px minimum touch target */ min-width: 100px; min-height: 48px; padding: 12px 24px;
font-size: 16px; font-weight: 600; border-radius: 8px; border: none; cursor: pointer; transition: all 0.2s;
/* Ensure text is selectable for screen readers */ user-select: text;}
.accessible-button.primary { /* Accessibility: 4.7:1 contrast (WCAG AA) */ background: #2196F3; color: #FFFFFF;}
.accessible-button.secondary { background: #FFFFFF; color: #2196F3; border: 2px solid #2196F3;}
.accessible-button:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);}
.accessible-button:focus { /* High-contrast focus indicator */ outline: 3px solid #FFEB3B; outline-offset: 2px;}
.accessible-button:disabled { opacity: 0.5; cursor: not-allowed;}
/* Keyboard-navigable menu */.keyboard-menu { list-style: none; padding: 0; margin: 0; display: flex; gap: 4px;}
.keyboard-menu a { display: block; padding: 12px 20px; min-height: 48px;
color: #212121; text-decoration: none; border-radius: 4px; transition: background 0.2s;
/* Align text vertically */ display: flex; align-items: center;}
.keyboard-menu a:hover { background: #F5F5F5;}
.keyboard-menu a:focus { /* Example high-contrast focus */ outline: 3px solid #FFEB3B; outline-offset: 2px; background: #FFF9C4; /* Light yellow background */}
.keyboard-menu a.active { background: #2196F3; color: #FFFFFF;}| Component | Keyboard Support | Screen Reader | Focus Management | Check |
|---|---|---|---|---|
| Buttons | Enter/Space | Announced as “button” + label | Visible focus | ✅ |
| Links | Enter | Announced as “link” + text | Visible focus | ✅ |
| Modals | Operable controls; no page-level keyboard trap | role="dialog" + name/description | Focus enters and returns | ✅ |
| Menus | Arrow keys, Home/End | role=“menu”, aria-expanded | Focus follows arrow | ✅ |
| Forms | Tab, Space/Enter | Labels + error messages | Focus on error | ✅ |
Treat this table as a QA checklist, not a claim that every BBC product has been audited by Human Standards. The cited GEL pages are the evidence for the intended patterns; results for a particular service still require testing that service.
Human Standards Integration: Analysis updated against cited primary documentation, August 2026