Skip to main content

Play+ Accessibility Helper: playa11y

Introduction

In the Play+ ecosystem, accessibility is not an afterthought—it’s a core component of our "Design that Breathes" philosophy. We believe that great design is inclusive design, and an application that isn’t accessible to everyone is incomplete. This guide is based on the principle of Inclusive by Design.

The playa11y helper provides a foundational toolkit to automate accessibility best practices, reduce boilerplate code, and empower developers to build experiences that are usable by everyone, regardless of their abilities. This aligns directly with our Inclusive pillar by embedding WCAG 2.2 AA standards directly into the development workflow, ensuring every user feels a sense of belonging and trust.


The Three Pillars of Accessibility Enforcement

1. Automated Tooling (Linter & CI)

  • Our playlint ESLint config includes the jsx-a11y plugin.
  • All PRs run an axe-core audit.
  • If critical violations are found, the build fails (non-negotiable quality gate).

2. Accessible-by-Default Components

  • All core Play+ components:

    • Include full keyboard navigation
    • Clear focus states
    • Compliant contrast ratios
    • Correct ARIA roles (e.g., modals, tabs)

3. Developer Practices & the playa11y Toolkit

  • Developers own the final layer of a11y.
  • This guide and toolkit make it easy to implement best practices.

Package Info

Package / PathDescription
Golden Path (Recommended)Pre-installed: /system/play.a11y.ts
Uplift Path (Coming Soon)@playplus/a11y

Folder Reference

DirectoryPurpose & Guidelines
/system/Core Play+ helpers (e.g., play.a11y.ts)
/config/User-overridable configs (e.g., play.a11y.config.json)

Helper - Pillars Alignment

PillarHow This Helper Aligns
InclusivePrimary Pillar: Enforces WCAG 2.2 AA for diverse ability support
IntuitiveEnsures predictable, seamless experience for assistive technologies
EngagingAllows graceful degradation of motion, ensuring delight without discomfort

Helper Overview

The playa11y helper is a lightweight utility that abstracts the complexity of accessibility. It automates common tasks, avoids boilerplate, and prevents errors.

Key Features

  • Live region announcements
  • Safe focus management
  • Reduced motion detection
  • Dev-only contrast ratio checks

Its goal is to make "the right way the easy way."


Config Options

Config VariableDefault ValueDescriptionRecommended Value
liveRegionPoliteness"polite"Default level for screen reader messages"polite"
focusRing.color"#E91E63"Color of focus ring (from token)$color-border-focus
focusRing.offset"2px"Distance between element and focus ring$space-1
focusRing.width"2px"Thickness of focus ring2px

Helper Methods

Method NameDescriptionSignature
announceAnnounces a message to screen readers`announce(message: string, politeness?: 'polite''assertive'): void`
focusSafely moves focus to an element`focus(element: HTMLElementnullundefined): void`
prefersReducedMotionDetects user's motion preferenceprefersReducedMotion(): boolean
getContrastCalculates contrast ratio (dev only)getContrast(color1: string, color2: string): number

Usage Examples

React: Managing Focus and Announcements

import React, { useRef, useEffect } from 'react';
import { playa11y } from '../system/play.a11y';

function AddUserForm({ onUserAdded }) {
const nameInputRef = useRef(null);
const successMessageRef = useRef(null);

useEffect(() => {
playa11y.focus(nameInputRef.current);
}, []);

const handleSubmit = (e) => {
e.preventDefault();
const newUserName = nameInputRef.current?.value || "New User";
onUserAdded(newUserName);

playa11y.announce(`User ${newUserName} was successfully added.`);
playa11y.focus(successMessageRef.current);
};

return (
<form onSubmit={handleSubmit}>
<label htmlFor="userName">User Name</label>
<input id="userName" ref={nameInputRef} type="text" />
<button type="submit">Add User</button>
<div ref={successMessageRef} tabIndex={-1} role="alert"></div>
</form>
);
}

Angular: Using Angular CDK

import { Component, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core';
import { LiveAnnouncer, FocusMonitor } from '@angular/cdk/a11y';

@Component({
selector: 'app-add-user-form',
template: `...`
})
export class AddUserFormComponent implements AfterViewInit {
@ViewChild('nameInput') nameInput!: ElementRef;
@ViewChild('successMessage') successMessage!: ElementRef;

private announcer = inject(LiveAnnouncer);
private focusMonitor = inject(FocusMonitor);

ngAfterViewInit() {
this.focusMonitor.focusVia(this.nameInput.nativeElement, 'program');
}

handleSubmit() {
this.announcer.announce('User successfully added.', 'polite');
this.focusMonitor.focusVia(this.successMessage.nativeElement, 'program');
}
}

The Play+ Accessibility Checklist

Semantic Structure

  • Use semantic HTML5 (<main>, <nav>, etc.)
  • Follow logical heading hierarchy (<h1> to <h2>)
  • Use <p>, <ul>, <ol> for text, avoid <div> for layout

Keyboard Navigation

  • All interactive elements are keyboard accessible
  • Focus order matches visual order
  • No keyboard traps

Forms & Inputs

  • Each input has a <label for="...">
  • Errors use aria-describedby
  • Buttons have meaningful labels

Images & Media

  • <img> elements have alt, or alt="" if decorative
  • Videos have captions/audio descriptions if needed

Dynamic Content

  • Use playa11y.announce() for updates
  • Manage focus for modals/tooltips
  • Minimize animation if prefersReducedMotion() is true

Manual Verification

  • Navigate with only keyboard
  • Use screen reader (VoiceOver, NVDA)
  • Test UI at 200% zoom

⚠️ Common Pitfalls

  • Icon-only buttons missing aria-label
  • Skipped heading levels
  • Custom controls without full keyboard support

Summary

Accessibility is a shared responsibility and a mark of true craftsmanship. At Play+, we make it automatic, built-in, and empowering.

  • 🧪 Catch Issues Early: Linters and CI audits
  • 🧱 Accessible Foundations: Ready-to-use, WCAG-compliant components
  • 🛠 Developer Empowerment: The playa11y helper and checklists