$npx -y skills add pixijs/pixijs-skills --skill pixijs-accessibilityUse this skill when adding screen reader and keyboard navigation to PixiJS v8 apps. Covers AccessibilitySystem options (enabledByDefault, debug, activateOnTab, deactivateOnMouseMove), per-container accessibility properties, shadow DOM overlay, mobile touch-hook activation. Trigge
| 1 | Enable screen reader and keyboard navigation via PixiJS's AccessibilitySystem. The system creates an invisible shadow DOM overlay positioned over accessible containers so assistive technology can discover and activate them. |
| 2 | |
| 3 | ## Quick Start |
| 4 | |
| 5 | ```ts |
| 6 | const button = new Sprite(await Assets.load("button.png")); |
| 7 | button.accessible = true; |
| 8 | button.accessibleTitle = "Play game"; |
| 9 | button.accessibleHint = "Starts a new game session"; |
| 10 | button.eventMode = "static"; |
| 11 | button.tabIndex = 0; |
| 12 | app.stage.addChild(button); |
| 13 | |
| 14 | app.renderer.accessibility.setAccessibilityEnabled(true); |
| 15 | |
| 16 | button.on("pointertap", () => startGame()); |
| 17 | ``` |
| 18 | |
| 19 | **Related skills:** `pixijs-events` (pointer/tap handlers), `pixijs-scene-dom-container` (HTML elements on canvas), `pixijs-application` (init options). |
| 20 | |
| 21 | **Key points:** |
| 22 | |
| 23 | - By default the system activates only after the user presses Tab. Set `enabledByDefault: true` in Application init for immediate activation. |
| 24 | - On mobile, the system creates a hidden touch hook; screen-reader focus activates accessibility for the whole session. |
| 25 | - The AccessibilitySystem requires the main thread; it is not available in a Web Worker. |
| 26 | |
| 27 | ## Core Patterns |
| 28 | |
| 29 | ### Container accessible properties |
| 30 | |
| 31 | ```ts |
| 32 | import { Container, Sprite } from "pixi.js"; |
| 33 | |
| 34 | const container = new Container(); |
| 35 | container.accessible = true; |
| 36 | container.accessibleTitle = "Navigation menu"; |
| 37 | container.accessibleHint = "Contains links to other pages"; |
| 38 | container.eventMode = "static"; // required for custom tabIndex to apply |
| 39 | container.tabIndex = 0; |
| 40 | container.accessibleType = "div"; // defaults to 'button' |
| 41 | |
| 42 | const sprite = new Sprite(); |
| 43 | sprite.accessible = true; |
| 44 | sprite.accessibleTitle = "Close dialog"; |
| 45 | sprite.accessibleText = "X"; // text content of the shadow div |
| 46 | sprite.eventMode = "static"; |
| 47 | sprite.tabIndex = 1; |
| 48 | ``` |
| 49 | |
| 50 | Available properties on any Container: |
| 51 | |
| 52 | - `accessible` (boolean) - enables the accessible overlay div |
| 53 | - `accessibleTitle` (string) - sets the `title` attribute on the shadow div |
| 54 | - `accessibleHint` (string) - sets the `aria-label` attribute |
| 55 | - `accessibleText` (string) - sets inner text content of the shadow div |
| 56 | - `accessibleType` (string) - HTML tag for the shadow element, defaults to `'button'` |
| 57 | - `tabIndex` (number) - tab order for keyboard navigation (only applied when `interactive` is true / `eventMode` is `'static'` or `'dynamic'`) |
| 58 | - `accessibleChildren` (boolean, default `true`) - when `false`, prevents child containers from being accessible |
| 59 | - `accessiblePointerEvents` (string) - CSS `pointer-events` value on the shadow div |
| 60 | |
| 61 | ### Custom tab order |
| 62 | |
| 63 | Give each accessible container a `tabIndex` to control the order assistive tech walks through them. Higher numbers come later; equal numbers fall back to scene-graph order. |
| 64 | |
| 65 | ```ts |
| 66 | menuButton.accessible = true; |
| 67 | menuButton.eventMode = "static"; |
| 68 | menuButton.tabIndex = 1; |
| 69 | |
| 70 | playButton.accessible = true; |
| 71 | playButton.eventMode = "static"; |
| 72 | playButton.tabIndex = 2; |
| 73 | |
| 74 | settingsButton.accessible = true; |
| 75 | settingsButton.eventMode = "static"; |
| 76 | settingsButton.tabIndex = 3; |
| 77 | ``` |
| 78 | |
| 79 | `tabIndex` is only forwarded to the shadow div when the container is `interactive` (`eventMode` is `'static'` or `'dynamic'`). Without that, the system clamps the div's tabIndex back to `0`, and the order you set is ignored. |
| 80 | |
| 81 | ### Programmatic control |
| 82 | |
| 83 | ```ts |
| 84 | import { Application } from "pixi.js"; |
| 85 | |
| 86 | const app = new Application(); |
| 87 | await app.init({ width: 800, height: 600 }); |
| 88 | |
| 89 | // Enable accessibility at runtime |
| 90 | app.renderer.accessibility.setAccessibilityEnabled(true); |
| 91 | |
| 92 | // Check current state |
| 93 | console.log(app.renderer.accessibility.isActive); |
| 94 | console.log(app.renderer.accessibility.isMobileAccessibility); |
| 95 | |
| 96 | // Full init options: |
| 97 | await app.init({ |
| 98 | accessibilityOptions: { |
| 99 | enabledByDefault: true, // activate immediately (default: false) |
| 100 | debug: true, // makes overlay divs visible (default: false) |
| 101 | activateOnTab: true, // Tab key activates system (default: true) |
| 102 | deactivateOnMouseMove: false, // stay active when mouse moves (default: true) |
| 103 | }, |
| 104 | }); |
| 105 | ``` |
| 106 | |
| 107 | The system can also be configured via static defaults before creating the Application: |
| 108 | |
| 109 | ```ts |
| 110 | import { AccessibilitySystem, Application } from "pixi.js"; |
| 111 | |
| 112 | AccessibilitySystem.defaultOptions.enabledByDefault = true; |
| 113 | AccessibilitySystem.defaultOptions.deactivateOnMouseMove = false; |
| 114 | |
| 115 | const app = new Application(); |
| 116 | await app.init(); |
| 117 | ``` |
| 118 | |
| 119 | ### Handling accessible interactions |
| 120 | |
| 121 | ```ts |
| 122 | import { Sprite } from "pixi.js"; |
| 123 | |
| 124 | const button = new Sprite(); |
| 125 | button.eventMode = "static"; |
| 126 | button.accessible = true; |
| 127 | button.accessible |