Accessibility
Accessibility Guidelines
Overview
Section titled “Overview”Accessibility is not optional - it’s a fundamental requirement. WEC Design System follows WCAG 2.1 Level AA as our baseline standard, ensuring our products are usable by everyone.
Standards
Section titled “Standards”WCAG 2.1 Level AA Requirements
Section titled “WCAG 2.1 Level AA Requirements”| Requirement | Description | WEC Implementation |
|---|---|---|
| Color contrast | 4.5:1 for normal text, 3:1 for large text | All text meets AA ratios |
| Keyboard access | All functionality available via keyboard | Full keyboard navigation |
| Focus visible | Clear focus indicators on all interactive elements | #0050AE (blue) or #FF0025 (red) |
| Text alternatives | Alt text for images, labels for icons | ARIA labels on all icons |
| Forms | Labels, error messages, instructions | Inline validation with errors |
| Headings | Logical heading structure (h1-h6) | Semantic HTML hierarchy |
| Links | Descriptive link text | Never “click here” |
Keyboard Navigation
Section titled “Keyboard Navigation”Standard Keyboard Interactions
Section titled “Standard Keyboard Interactions”| Key | Action | Usage |
|---|---|---|
Tab | Move focus forward | Navigate through interactive elements |
Shift + Tab | Move focus backward | Navigate backwards |
Enter / Space | Activate | Buttons, links, checkboxes |
Escape | Close/Cancel | Modals, dropdowns, overlays |
Arrow Keys | Navigate | Within components (menus, tabs, grids) |
Home / End | Jump to start/end | Lists, grids, sliders |
Page Up / Page Down | Scroll by page | Long content areas |
Focus Management
Section titled “Focus Management”// Example: Focus trap in modalfunction Modal({ isOpen, onClose }) { const modalRef = useRef();
useEffect(() => { if (isOpen) { // Focus first interactive element const firstFocusable = modalRef.current?.querySelector( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); firstFocusable?.focus();
// Trap focus within modal const handleTab = (e) => { const focusable = modalRef.current?.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const first = focusable[0]; const last = focusable[focusable.length - 1];
if (e.key === 'Tab' && e.shiftKey && document.activeElement === first) { e.preventDefault(); last?.focus(); } else if (e.key === 'Tab' && !e.shiftKey && document.activeElement === last) { e.preventDefault(); first?.focus(); } };
document.addEventListener('keydown', handleTab); return () => document.removeEventListener('keydown', handleTab); } }, [isOpen]);
return ( <div ref={modalRef} role="dialog" aria-modal="true"> {/* Modal content */} </div> );}Return Focus After Close
Section titled “Return Focus After Close”function Dropdown({ trigger, children }) { const [isOpen, setIsOpen] = useState(false); const triggerRef = useRef();
const handleClose = () => { setIsOpen(false); triggerRef.current?.focus(); // Return focus to trigger };
return ( <> <button ref={triggerRef} onClick={() => setIsOpen(true)} aria-expanded={isOpen} > {trigger} </button> {isOpen && ( <div onBlur={handleClose}> {children} </div> )} </> );}Focus States
Section titled “Focus States”Focus Colors
Section titled “Focus Colors”WEC Design System uses two focus colors:
| Context | Color | Hex | Usage |
|---|---|---|---|
| Primary focus | Blue | #0050AE | Most interactive elements |
| Secondary focus | Red | #FF0025 | Primary buttons, brand elements |
Focus Indicator Styles
Section titled “Focus Indicator Styles”/* Primary focus (blue) */.button:focus-visible,.input:focus-visible,.link:focus-visible { outline: 2px solid #0050AE; outline-offset: 2px;}
/* Secondary focus (red for primary actions) */.button-primary:focus-visible { outline: 2px solid #FF0025; outline-offset: 2px;}
/* Ensure focus indicator is visible on all backgrounds */.card:focus-visible { outline: 2px solid #0050AE; outline-offset: -2px; /* Inside the card */}Focus Order
Section titled “Focus Order”Logical tab order (left-to-right, top-to-bottom):
<!-- Good: Logical tab order --><nav> <a href="/">Home</a> <a href="/products">Products</a> <a href="/about">About</a></nav>
<main> <h1>Products</h1> <button>Buy Now</button></main>
<footer> <a href="/contact">Contact</a></footer>Skip Links
Section titled “Skip Links”Provide skip links for keyboard users:
<a href="#main-content" class="skip-link"> Skip to main content</a>
<main id="main-content"> <!-- Main content --></main>.skip-link { position: absolute; top: -100%; left: 50%; transform: translateX(-50%); background: #001A41; color: white; padding: 12px 24px; border-radius: 0 0 8px 8px; text-decoration: none; font-weight: 500; z-index: 10000; transition: top 0.2s;}
.skip-link:focus { top: 0; outline: 2px solid #FF0025;}Color and Contrast
Section titled “Color and Contrast”Contrast Requirements
Section titled “Contrast Requirements”| Text Size | Minimum Contrast | WEC Colors |
|---|---|---|
| Normal text (< 18px) | 4.5:1 | Grey 100 on white passes |
| Large text (18px+) | 3:1 | All headings pass |
| UI components | 3:1 | All buttons/badges pass |
Verified Contrast Ratios
Section titled “Verified Contrast Ratios”| Foreground | Background | Contrast Ratio | Status |
|---|---|---|---|
Grey 100 (#4E5764) | White (#ffffff) | 8.2:1 | ✅ AA + AAA |
Telkomsel Red (#FF0025) | White (#ffffff) | 4.5:1 | ✅ AA |
Dark Blue (#001A41) | White (#ffffff) | 13.5:1 | ✅ AA + AAA |
Success (#008E53) | Success Light (#EDFCF0) | 5.1:1 | ✅ AA |
Error (#BC1D42) | Error Light (#FDDDD4) | 4.8:1 | ✅ AA |
Info (#0050AE) | Info Light (#E9F6FF) | 6.2:1 | ✅ AA + AAA |
Warning (#FDA22B) | Warning Light (#FEF3D4) | 2.8:1 | ⚠️ Use darker text |
Do’s and Don’ts
Section titled “Do’s and Don’ts”| ✅ Do | ❌ Don’t |
|---|---|
| Use color + icons for status | Use only red for errors |
| Check contrast ratios | Use light gray text on white |
| Support dark/light modes | Hardcode colors without alternatives |
| Test with color blindness tools | Assume everyone sees color same |
| Use underlines for links | Rely on color alone for links |
Testing Color Contrast
Section titled “Testing Color Contrast”Use these tools to verify contrast:
Screen Reader Support
Section titled “Screen Reader Support”Semantic HTML
Section titled “Semantic HTML”<!-- Good: Semantic elements --><nav aria-label="Main navigation"> <ul> <li><a href="/">Home</a></li> <li><a href="/products">Products</a></li> </ul></nav>
<main> <article> <h1>Product Name</h1> <p>Description...</p> </article></main>
<aside aria-label="Related products"> <!-- Sidebar content --></aside>
<footer> <p>© 2025 WEC Design System</p></footer>ARIA Attributes
Section titled “ARIA Attributes”Use ARIA to enhance, not replace, semantic HTML:
<!-- Add labels to icon-only buttons --><button aria-label="Close dialog"> <svg>...</svg></button>
<!-- Expandable sections --><button aria-expanded="false" aria-controls="faq-content"> FAQ</button><div id="faq-content" hidden> Answer content...</div>
<!-- Announce dynamic content --><div role="status" aria-live="polite"> Form submitted successfully</div>
<!-- Hide decorative elements --><span aria-hidden="true">✓</span>
<!-- Label form inputs --><label for="email">Email address</label><input id="email" type="email" aria-describedby="email-hint" aria-invalid="false" aria-required="true"/><span id="email-hint">We'll never share your email</span><span id="email-error" role="alert" aria-live="assertive"> Please enter a valid email</span>ARIA Roles Reference
Section titled “ARIA Roles Reference”| Role | When to Use | Example |
|---|---|---|
button | Non-button elements acting as buttons | <div role="button"> |
link | Non-link elements acting as links | <div role="link"> |
navigation | Site navigation areas | <nav role="navigation"> |
main | Main content area | <main role="main"> |
complementary | Sidebars | <aside role="complementary"> |
search | Search functionality | <form role="search"> |
dialog | Modal dialogs | <div role="dialog"> |
alert | Error messages | <div role="alert"> |
status | Status updates | <div role="status"> |
progressbar | Progress indicators | <div role="progressbar"> |
Accessible Form Structure
Section titled “Accessible Form Structure”<div class="form-group"> <label for="email"> Email address <span class="required" aria-label="required">*</span> </label>
<input id="email" type="email" placeholder="name@example.com" aria-describedby="email-hint email-error" aria-invalid="false" required />
<span id="email-hint" class="form-hint"> We'll send confirmation to this email </span>
<span id="email-error" class="form-error" role="alert" hidden> Please enter a valid email address </span></div>Error Handling
Section titled “Error Handling”- Clear error messages linked to inputs via
aria-describedby - Inline validation with helpful feedback
- Don’t rely solely on color for errors
- Announce errors to screen readers with
role="alert"andaria-live="assertive"
function FormInput({ label, error, hint, ...props }) { const errorId = `${props.id}-error`; const hintId = `${props.id}-hint`;
return ( <div class="form-group"> <label for={props.id}>{label}</label> <input {...props} aria-invalid={!!error} aria-describedby={`${error ? errorId : ''} ${hint ? hintId : ''}`.trim()} /> {hint && !error && ( <span id={hintId} class="form-hint">{hint}</span> )} {error && ( <span id={errorId} class="form-error" role="alert"> {error} </span> )} </div> );}Required Fields
Section titled “Required Fields”<!-- Always indicate required fields --><label for="phone"> Phone number <span class="required" aria-label="required">*</span></label><input id="phone" type="tel" aria-required="true" required/>Touch Targets
Section titled “Touch Targets”Ensure interactive elements are large enough for touch:
| Element | Minimum Size | WEC Standard |
|---|---|---|
| Buttons | 44×44px | 48×48px recommended |
| Links | 44×44px | Wrap small text in padding |
| Form inputs | 44px height | 48px recommended |
| Checkboxes | 24×24px | With padding to 44px |
| Radio buttons | 24×24px | With padding to 44px |
Adding Padding to Small Touch Targets
Section titled “Adding Padding to Small Touch Targets”/* Icon button - make entire area touchable */.icon-button { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; padding: 12px; /* Creates space around icon */}
/* Text link - add padding */.inline-link { display: inline-block; padding: 8px 12px; margin: -8px -12px; /* Negative margin prevents layout shift */}Images and Media
Section titled “Images and Media”Alt Text Guidelines
Section titled “Alt Text Guidelines”<!-- Descriptive alt text --><img src="product.jpg" alt="Red smartphone on white background">
<!-- Decorative images --><img src="decoration.svg" alt="" role="presentation">
<!-- Functional images --><img src="search-icon.svg" alt="Search">
<!-- Images with text content --><img src="sale-banner.jpg" alt="Flash sale: 50% off all items this weekend only">
<!-- Complex images - use longdesc --><img src="chart.png" alt="Sales trend chart" longdesc="chart-description.html">Audio and Video
Section titled “Audio and Video”<!-- Video with captions --><video controls> <source src="video.mp4" type="video/mp4"> <track kind="captions" src="captions.vtt" srclang="en" label="English"></video>
<!-- Audio with transcript --><audio controls> <source src="audio.mp3" type="audio/mpeg"></audio><a href="transcript.html">Read transcript</a>Testing Checklist
Section titled “Testing Checklist”Before shipping, test with:
Keyboard Only
Section titled “Keyboard Only”- Unplug mouse
- Tab through all interactive elements
- Verify logical tab order
- Test Enter/Space on buttons
- Test Escape for closing modals
- Test arrow keys in menus
Screen Reader
Section titled “Screen Reader”- NVDA (Windows, free)
- JAWS (Windows)
- VoiceOver (macOS/iOS)
- TalkBack (Android)
Browser Tools
Section titled “Browser Tools”- Chrome DevTools Lighthouse accessibility audit
- Firefox Accessibility Inspector
- axe DevTools extension
Zoom and Resize
Section titled “Zoom and Resize”- 200% browser zoom
- 400% browser zoom
- Text-only zoom (up to 200%)
- Mobile viewport (320px width)
Additional Tools
Section titled “Additional Tools”- High contrast mode (Windows)
- Color blindness simulator
- Automated accessibility testing