Skip to content

The Parallel DOM (PDOM)

API reference vs. author guide

This page explains why and how simulation authors opt Nodes into the PDOM. For the full list of ParallelDOM trait options and accessors on Node, see ParallelDOM.

Scenery draws to Canvas/SVG/WebGL: To make applications accessible, scenery maintains a Parallel DOM: a hidden tree of real HTML elements that mirrors the interactive structure of the scene graph. Screen readers read it, keyboard focus moves through it, and input events on it are routed back to the corresponding Nodes.

You opt each Node into the PDOM with accessibility options — no separate accessibility tree to maintain.

Basic options

ts
import { Node, Rectangle } from 'scenerystack/scenery';

// A heading + paragraph in the PDOM
const infoSection = new Node( {
  tagName: 'div',
  labelTagName: 'h3',
  labelContent: 'Mass Controls',
  descriptionContent: 'Adjust the mass of each body, in kilograms.'
} );

// An interactive element: focusable, named, with help text
const resetShape = new Rectangle( 0, 0, 40, 40, {
  tagName: 'button',
  accessibleName: 'Reset Masses',
  accessibleHelpText: 'Return both masses to their starting values.'
} );
OptionMeaning
tagNameHTML element representing this Node ('div', 'button', 'ul', …). Setting it is what places the Node in the PDOM
innerContentText content inside the element
labelTagName / labelContentA sibling label element (e.g. an h3 before the content)
descriptionContentA sibling description paragraph
accessibleNameThe name announced by screen readers (higher-level API; prefer this)
accessibleHelpTextSupplementary guidance announced with the element
focusableWhether the element is in the tab order (interactive tagNames like 'button' already are)

Common UI components from sun (buttons, sliders, checkboxes) come with sensible PDOM structure built in — you typically only supply accessibleName and accessibleHelpText.

Focus order with pdomOrder

By default, PDOM order follows scene-graph order, which is a rendering order and often wrong for traversal. Set pdomOrder on a parent to control it explicitly:

ts
screenView.pdomOrder = [
  massControlPanel,
  bodyNode,        // draggable — see /patterns/drag-listeners
  gridCheckbox,
  resetAllButton
];

Nodes omitted from pdomOrder follow afterwards in their natural order; null may be used as a placeholder for "everything else here".

Custom interactions

A Node that implements its own interaction (like a custom draggable) needs three things:

ts
bodyNode.tagName = 'div';
bodyNode.focusable = true;
bodyNode.accessibleName = 'Movable planet';
bodyNode.addInputListener( new KeyboardDragListener( { /* ... */ } ) );
  1. A tagName so it exists in the PDOM,
  2. focusable: true so keyboard users can reach it,
  3. listeners that respond to keyboard input, not just pointers — see Drag Listeners.

Test with the keyboard first

Before reaching for a screen reader, unplug your mouse: every interactive element should be reachable with Tab, operable with Enter/Space/arrows, and show a visible focus highlight (scenery draws these automatically for focusable Nodes).