Prior to reading through the following documentation, please familiarize yourself with this background information about accessible HTML, the accessibility tree, accessible names, and ARIA. The rest of this document will assume you have knowledge of these concepts.
Please note that some of the information in this document is specific to PhET Interactive Simulations.
For more information on the implementation of accessibility in Scenery, see the Accessibility Implementation Notes.
Scenery uses HTML5 technologies (svg, canvas, webgl) to draw the display. These have very little semantic
data as to what is inside the rendered graphic. The PDOM (parallel DOM (document object model)) pulls semantic
data from the scene graph and adds it to a separate HTML structure that is available for assistive technologies
When we say PDOM, think the HTML manifestation of the graphical
Node content in the display.
The parallel DOM is an invisible structure that runs alongside the graphics. It is a basic HTML document that
represents active scenery elements. It provides an interface to assistive technologies so that they have
a representation of the display at a given state. The PDOM is dynamic and its DOM tree will update with changes
to the scene. Any
Node that has accessible content specified will be represented in the parallel DOM.
is used so that scenery can rely on semantic HTML and accessibility conventions of the web. This way, scenery can
push some of
the accessibility work load to the browser and AT for providing keyboard navigation and auditory descriptions.
Node in scenery, even those that are not visible or pickable, can have accessible content. The
content will represent the node in the parallel DOM tree.
Most of Scenery's accessibility features are defined and implemented in
Accessibility.js is a trait that is mixed in to
Node.js. It adds getters and setters
for accessibility properties, so all we have to do is pass in a11y specific options like normal into the
Parent.call() or mutate() function calls.
The following explains how to use the accessibility functionality of Scenery. For more information and up-to-date api documentation, see the source code. On the side bar, options are categorized by where they are introduced and explained. In this file there is little "traditional" documentation, rather example based explanation. The source code is the best place for specifics and implementation.
The primary way that developers will implement a11y is through options passed through to
First off, each
Node that wants content in the PDOM will need an HTML element in the PDOM to
To do this, use the
Above is a simple scenery
Rectangle, that is represented as a paragraph tag in the PDOM.
I also gave the
<p> text content inside the html element.
Up to this point there has been a one to one correlation of Nodes to HTML elements in the PDOM,
but the a11y api supports a richer architecture to represent a
Node to AT: each
Node can have multiple DOM Elements. A
Node's HTML Elements, or the
Node's peer, can be thought of
as collectively equating to the
Node. (Note: a "peer" is of type
Node has a primary HTML element (the
HTMLElement that is created with the
tagName option), this is only a single element of the peer. Other HTML Elements can
provide supplemental information to be conveyed
to assistive technology. Label and description elements can be added, as well as a parent element
to contain all HTML Element "siblings" for a Node's peer. In total a peer can have up to
four HTML Elements: the primary sibling, label sibling, description sibling, and the container parent of these
Terminology is key in understanding the specifics of creating the PDOM. From here on, when speaking
about "siblings," we are speaking about the relationship between HTML elements in
the PDOM. These Elements are not "siblings to the
Node," but instead only siblings to each other,
with an HTML Element parent called the "containerParent".
Node has an
AccessiblePeer (aka “peer”) that
HTMLElements (aka “elements”) that are related to that
in the PDOM. A
Node has one or more associated elements, one
of which is the “primary element”, whose tag is specified by option
tagName. There are two other
optional “supplementary elements”, whose tags are specified via options
descriptionTagName. If more than the primary element is specified, they are all referred to as
“sibling elements” (including the "primary sibling") and are automatically grouped together under a
“container element”. The container element is given a default tag, which can be overridden with
Here is an example of a
Node that uses all of its elements to provide the fullest
semantic picture of the sim component to the PDOM.
In this example, the rectangle's primary sibling is a button with an Accessible Name of "Grab Magnet". It has a
sibling with an
h3 tag with inner content "Grab Magnet", and a description sibling with a
tagName of "p" with the specified sentence.
A few notes here:
AccessibilityUtilfor the default tag names.
Textnode "North South Magnet" has no accessible content and so it does not appear anywhere in the PDOM.
By default, the PDOM hierarchy will match the hierarchy of the scene graph. This is an important feature to
consider. If a parent
Node and child
Node both have accessible content, then, in the
PDOM, the accessible HTML
of the child node will be added as a child of the parent's primary sibling. In scenery code, this is
AccessiblePeer, a type that stores and controls all HTML Elements for a given
Consider the following example where we have a box filled with circles and the desired a11y representation is an unordered list filled with list items.
In this example, scenery automatically structured the PDOM such that the list items are children of the unordered list to match the hierarchy of the scene graph.
The a11y api can provide lots of flexibility in how to display content in the PDOM. Each sibling of the peer
has a name (like label or description), but at its core it is still just an HTML element, and it can be any
tag name specified. Below is an example of a
Node that is used just to add text content to the
PDOM. In looking at the example, remember that there are default tag names for supplementary peer
Elements. (Note: as of writing this, sibling tag names default to "p").
In this sense, the naming of the options to control each sibling in a bit "arbitrary," because you can use the api for what will work best for the situation. Every Node does not necessarily require all four HTML Elements of its peer in the PDOM, use your judgement.
The a11y api supports keyboard navigation only on the Node's primary sibling. A general philosophy to follow is to have the DOM Element hold as much semantic information as possible. For example, if there is a button in the sim, it is an obvious choice to use a "button" element as the Node's primary sibling tag. Another solution that works, although it is much worse, would be to choose a div, and then add listeners manually to control that div like a button. As a "div", an AT will not be able to tell the user what the element is. In general try to pick semantic html elements that will assist in conveying as much meaning as possible to the user. Although it is possible to use the ARIA spec to improve accessible experience, it should be used as little as possible because it has minimal support. Addressing semantics any further goes beyond the scope of this document.
If you specify a
tagName: 'input', then use the
inputType option to fill in the "type"
attribute of the element. There are also
accessibleChecked options to
manipulate specific and common (that we found) attributes of input tags. If you need more control of the primary
DOM element's attributes, see
The above example is a Node whose PDOM representation is that of a basic checkbox. In order to give it
Node.addAccessibleInputListener(). The function takes in type
Object.<string, function> where the key is the name of the DOM Event you want to listen to.
event is more often than not different than the listener needed for a mouse. Don't forget to remove the listener
Node is disposed with
All interactive elements in the PDOM receive keyboard focus, but not all objects in the display are interactive. For example, using PhET Interactive Simulations, the Sweater in Balloons and Static Electricity is a dynamic content object because its electrons can be transferred to a balloon. Even so it is not directly interacted with by the user, thus the sweater never receives focus.
When an element in the PDOM is focused, a focus highlight is automatically rendered in the display to support
keyboard navigation. For more complex interactions, type
input, or other native and focusable
elements, may not work. Other tag names can be focused with the
focusable option. The ARIA attribute
role can help inform the user to the custom interaction (use the
ariaRole option). For
example using the ARIA "application" role has worked well for freely moving, draggable objects.
This will add a tab index of 0 to the element. Focusable elements can be manually focussed and blurred using the
Node.blur() functions. If a specific focus highlight is desired, a
Shape can be passed into the
Visibility in the PDOM and the focus order is directly effected by
Node.visible, but can
also be toggled independently with the option
Node.accessibleVisible. When set to true this
will hide content from screen readers and remove the element from focus order.
Most properties of the
Accessibility.js trait are mutable so that the Parallel DOM can update
with the graphical scene. Here are a few examples:
tagName: set/get the tag name of the primary DOM sibling of a node
labelTagName: set/get the tag name of the label DOM sibling for a node
descriptionTagName: set/get the tag name for the description DOM sibling of a node
innerContent: set/get the text content of primary sibling of a node
labelContent: set/get the text content of label sibling for a node
descriptionContent: set/get the text content of description sibling for a node
ariaRole: set/get the ARIA role for the primary DOM sibling for a node
Up to this point these have only been offered as options, but each of these can be dynamically set also. Setting
any of the
.*[tT]agName setters to
null will clear that element from the PDOM. If you
Node.tagName = null, this will clear all accessible content of the node.
Please see the Accessibility trait for a complete and up-to-date list of getters/setters.
The Accessible Name of an element is how assistive technology identifies an element in the browser's accessibility tree. Diving into the nuance of this idea goes beyond the scope of this document, but understanding this is imperative to successfully creating an accessible PDOM. For more info see background reading about the topic.
Here is an overview about the various ways to set the Accessible Name via the Scenery a11y api.
<button>My Button</button>. To accomplish this with the a11y api use
labelElement: a label element can be associated with an interactive
inputtype that does not have inner content in order to provide the input with an accessible name. A label is the preferred naming method when the display's interaction has visible text-based identifying it on screen. A label element can only be associated with "labelable" elements like typical interactive HTML elements. To add an Accessible Name via a
label, set the
labelTagNameto "label" and the "for" attribute will automatically be filled in to point to the primary sibling.
aria-labelAttribute: an ARIA attribute that can provide an accessible name. For the a11y api use the
ariaLabeloption to set the value of said attribute on the primary DOM Element.
aria-labelledbyAttribute: this can be used to associate an HTML element other than the label element to another element. The elements do not have to be right beside each other. TODO: the a11y api docs to come. Also add describedby? see phetsims/scenery#701
Node.accessibleOrder = . Scenery supports a fully independent tree of AccessibleInstances to order the PDOM versus the ordering based on the
Nodes into the
Instancetree. Because of this, you can use
Node.accessibleOrderto largely remap the scene graph (for rendering into the PDOM) without affecting the visually rendered output.
Node.accessibleOrdertakes any array of
Nodes, even if the they aren't children to that
All interactive alerts are powered with the aria-live attribute.
PhET manages alerts in a custom queue, see utteranceQueue.js
(note this is in repo scenery-phet) for more information. All
PhET alerts should go through utteranceQueue,
aria-live should not be added to elements in the
The following information is specific to PhET's work with accessibility and this api.
Accessibility features are hidden behind an
?accessibility query parameter. This hides a11y features
until they are ready for production. When ready, accessibility can be enabled by default by adding
accessibility: true to
Accessibility specific strings are not yet translatable. That being said PhET has every intention of getting to the point, we just don't have the infrastructure set up yet. Keeping that in mind, please follow the following conventions. This will help us greatly when it comes time to move these a11y strings into the translatable strings json file.
string!plugin. This means:
OhmsLawA11yStrings.jsfor an model example.
When beginning a11y work in a simulation, add
"accessible": true to the sim's package.json.
Then regenerate the lists to add the simulation to perennial/data/accessibility list, and generate an
a11y-view HTML document to assist with development (
The 'A11y view' is an HTML page that runs the simulation in an iframe and shows an up-to-date copy of the PDOM
next to the sim. It can be used to assist in development of accessibility features by allowing you to see the
accessible labels, descriptions, and alerts without requiring screen reader testing. This should be generated
by Bayes, but it can be generated manually with
grunt generate-a11y-view-html in the sim repo.
The PDOM in PhET sims are specific and designed to give the most semantic and pedagogical information possible.
These priorities can differ from those displayed in the visual simulation. We use
Node.accessibleOrder to help manage this discrepancy. As a PhET Developer, please use the following
guide to develop the a11y ordering in the PDOM versus the traditional rendering order of the scene graph. Each
item in the list is ranked, such that you should start with item 1, and then if that doesn't work for your
situation, try the next item down.
NOTE: This list was created with a mindset of instrumenting a simulation with accessibility. If a new sim is being created, then likely this list is irrelevant because the design process from the beginning will be focused on accessibility and regular sim development together.
setAccessibleOrderon local children, if not. . .
setAccessibleOrderon descendants (can be local vars like
setAccessibleOrder is needed on Nodes that are not descendants, then likely there is a structural
issue that needs to be addressed fully, rather than hacked at by using
although the setter will accept any
Node in the scene graph.
// a11ycomment as a header.
aria-labelledbyattribute. With this association the H2's content, "Play Area", provides the region with an accessible name in the Accessibility Tree which is accessed by assistive technology.
Please discuss developer related questions or problems with @jessegreenberg, @zepumph, or @mbarlow12 and update this document accordingly to help those who follow in your footsteps. Also @terracoda is a great resource on questions about ARIA and accessibility in general.
For up-to-date documentation and the latest API for accessibility in Scenery, please visit the source code.
Good luck and happy coding!