Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

ARIA Attributes for Interactive JSON Tree Views

JSON tree views are a common way to visualize hierarchical data, especially in developer tools, data explorers, and configuration interfaces. They present complex data structures in a navigable and digestible format. However, for users relying on screen readers or other assistive technologies, a purely visual representation isn't enough. To make these components truly inclusive, we must employ Accessible Rich Internet Applications (ARIA) attributes.

This article explores the essential ARIA roles and attributes needed to transform a standard interactive JSON tree view into an accessible one, ensuring navigation and understanding for all users.

Why ARIA for Tree Views?

HTML provides basic structural elements, but it doesn't have a native element for a complex interactive tree structure like a JSON view. Assistive technologies need programmatic cues to understand the component's purpose, structure, and state. ARIA provides these cues through roles, states, and properties.

Properly implemented ARIA for a tree view allows users to:

  • Understand that the component is a tree structure.
  • Navigate between nodes (objects, arrays, key-value pairs) using standard keyboard commands (like arrow keys).
  • Know the relationship between nodes (parent/child).
  • Understand the state of a node (e.g., expanded or collapsed).
  • Identify their current position within the tree.

Core ARIA Roles

role="tree"

This role is applied to the main container element of the tree view. It identifies the element as a tree widget. This tells assistive technologies to interpret the contained structure as a tree. The element with this role should typically have the keyboard focus, and manage focus internally on the selected treeitem.

Example: Tree Container

<ul role="tree" aria-labelledby="json-tree-label">
  <!-- Tree items go here -->
</ul>

<!-- The label element associated with the tree -->
<span id="json-tree-label" className="sr-only">JSON Data Structure</span>

Note the use of aria-labelledby to provide an accessible name for the tree, pointing to an element containing the label. The sr-only class is common for visually hidden elements accessible to screen readers.

role="treeitem"

This role is applied to each node in the tree. In a JSON tree view, this would typically be the element representing a key-value pair, an item in an array, or a nested object/array node that can be expanded/collapsed. A treeitem must be contained within an element with role tree or group.

Example: Basic Tree Item (Leaf Node)

<li role="treeitem">
  <span>
    <span>key</span>: <span>"value"</span>
  </span>
</li>

This represents a simple key-value pair, which is a leaf node (cannot be expanded).

role="group"

This role is used for elements that contain child treeitems. In a JSON tree, this would be the container (<ul> or <ol>) immediately under an expandable treeitem node (representing a nested object or array). This groups the children belonging to that parent node.

Example: Expandable Tree Item with Group

&lt;li role="treeitem" aria-expanded="false"&gt;
  &lt;span&gt;
    &lt;span&gt;nestedObject&lt;/span&gt;: &lt;span&gt;&#x7b;&#x7d;&lt;/span&gt; &lt;!-- Represents the object --&gt;
    &lt;button aria-label="Expand nestedObject"&gt;
        &lt;ChevronRight size={16} /&gt; &lt;!-- Example expand icon --&gt;
    &lt;/button&gt;
  &lt;/span&gt;
  &lt;ul role="group"&gt;
    &lt;!-- Child tree items for nestedObject go here --&gt;
    &lt;li role="treeitem"&gt;...&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;

The <li role="treeitem"> represents the parent node (nestedObject). The <ul role="group"> contains its children. Note that the role="group" element is typically hidden when aria-expanded="false" on the parent treeitem.

Essential ARIA States and Properties

aria-expanded

Applied to a treeitem that can be expanded or collapsed (i.e., parent nodes like objects or arrays).

  • aria-expanded="false": The node is currently collapsed.
  • aria-expanded="true": The node is currently expanded.
  • aria-expanded="undefined" (or attribute not present): The node is a leaf node and cannot be expanded.

Crucially, the visual state (whether the children are visible) must match the aria-expanded value. The interactive element that toggles the expanded state (like a button or the treeitem itself if it's a link/button) should control this attribute on the treeitem element.

Example: Toggling Expansion

&lt;li role="treeitem" aria-expanded="false"&gt;
  &lt;span&gt;
    &lt;span&gt;anArray&lt;/span&gt;: &lt;span&gt;[...]&lt;/span&gt;
    &lt;button aria-label="Expand anArray"&gt;
        &lt;!-- Icon changes based on aria-expanded state (visually and semantically) --&gt;
        &lt;ChevronRight size={16} /&gt;
    &lt;/button&gt;
  &lt;/span&gt;
  &lt;ul role="group" className="hidden"&gt; &lt;!-- This group is hidden when aria-expanded="false" --&gt;
    &lt;li role="treeitem"&gt;...&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;

&lt;!-- When expanded --&gt;
&lt;li role="treeitem" aria-expanded="true"&gt;
  &lt;span&gt;
    &lt;span&gt;anArray&lt;/span&gt;: &lt;span&gt;[...]&lt;/span&gt;
    &lt;button aria-label="Collapse anArray"&gt;
        &lt;ChevronDown size={16} /&gt;
    &lt;/button&gt;
  &lt;/span&gt;
  &lt;ul role="group" className=""&gt; &lt;!-- This group is visible when aria-expanded="true" --&gt;
    &lt;li role="treeitem"&gt;...&lt;/li&gt;
    &lt;li role="treeitem"&gt;...&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;

aria-selected

Applied to a treeitem to indicate if it is currently selected. While a JSON tree view might not always support item selection, if it does (e.g., clicking an item highlights it or loads its details elsewhere), this attribute should be used.

  • aria-selected="false": Not selected.
  • aria-selected="true": Selected.

This attribute is distinct from focus. Only one item should typically be focused at a time, but multiple items could potentially be selected depending on the interaction model.

Example: Selected Tree Item

&lt;li role="treeitem" aria-selected="true"&gt;
  &lt;span&gt;
    &lt;span&gt;selectedKey&lt;/span&gt;: &lt;span&gt;123&lt;/span&gt;
  &lt;/span&gt;
&lt;/li&gt;

aria-level

Applied to each treeitem to indicate its depth in the tree hierarchy. The value is an integer greater than or equal to 1. The tree element itself is level 0 conceptually, its direct children are level 1, their children are level 2, and so on.

Example: aria-level

&lt;ul role="tree"&gt;
  &lt;li role="treeitem" aria-level="1" aria-expanded="true"&gt;
    &lt;span&gt;root&lt;/span&gt;
    &lt;ul role="group"&gt;
      &lt;li role="treeitem" aria-level="2"&gt;
        &lt;span&gt;key1&lt;/span&gt;: &lt;span&gt;"value1"&lt;/span&gt;
      &lt;/li&gt;
      &lt;li role="treeitem" aria-level="2" aria-expanded="false"&gt;
        &lt;span&gt;key2&lt;/span&gt;: &lt;span&gt;&#x7b;&#x7d;&lt;/span&gt;
        &lt;ul role="group" className="hidden"&gt;
           &lt;li role="treeitem" aria-level="3"&gt;...&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

aria-posinset and aria-setsize

These attributes are used together on a treeitem to indicate its position within the set of its siblings at the same level.

  • aria-posinset: The position of the item within the set (1-based index).
  • aria-setsize: The total number of items in the set (siblings at the same level).

These are important for screen readers to announce "Item 1 of 5", "Item 2 of 5", etc., giving the user context about their location and the size of the current list of siblings.

Example: aria-posinset and aria-setsize

&lt;ul role="tree"&gt;
  &lt;li role="treeitem" aria-level="1" aria-posinset="1" aria-setsize="1" aria-expanded="true"&gt;
    &lt;span&gt;root&lt;/span&gt;
    &lt;ul role="group"&gt;
      &lt;li role="treeitem" aria-level="2" aria-posinset="1" aria-setsize="3"&gt;
        &lt;span&gt;key1&lt;/span&gt;: &lt;span&gt;"value1"&lt;/span&gt;
      &lt;/li&gt;
      &lt;li role="treeitem" aria-level="2" aria-posinset="2" aria-setsize="3" aria-expanded="false"&gt;
        &lt;span&gt;key2&lt;/span&gt;: &lt;span&gt;&#x7b;&#x7d;&lt;/span&gt;
        &lt;ul role="group" className="hidden"&gt;
           &lt;li role="treeitem" aria-level="3" aria-posinset="1" aria-setsize="1"&gt;...&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li role="treeitem" aria-level="2" aria-posinset="3" aria-setsize="3"&gt;
        &lt;span&gt;key3&lt;/span&gt;: &lt;span&gt;true&lt;/span&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

aria-labelledby and aria-describedby

These attributes associate a treeitem with other elements that provide its accessible name and description.

  • aria-labelledby: Refers to the ID of the element(s) that provide the label for the treeitem. For a JSON key-value pair, this would typically point to the element displaying the key name. For an array item, it might point to an element indicating its index, or the value itself if it's a simple value.
  • aria-describedby: Refers to the ID of the element(s) that provide a description for the treeitem. This could be used to link to the element displaying the value, the type of the value (object, array, string, number, etc.), or other relevant information.

Example: Using aria-labelledby and aria-describedby

&lt;li role="treeitem" aria-level="1" aria-posinset="1" aria-setsize="2"
    id="node-user" aria-labelledby="node-user-label" aria-describedby="node-user-value" aria-expanded="true"&gt;
  &lt;span&gt;
    &lt;span id="node-user-label"&gt;user&lt;/span&gt;: &lt;span id="node-user-value"&gt;&#x7b;&#x7d;&lt;/span&gt; &lt;!-- Visually shows { } --&gt;
    &lt;button aria-label="Expand user object"&gt;
        &lt;ChevronDown size={16} /&gt;
    &lt;/button&gt;
  &lt;/span&gt;
  &lt;ul role="group"&gt;
    &lt;li role="treeitem" aria-level="2" aria-posinset="1" aria-setsize="1"
        id="node-user-name" aria-labelledby="node-user-name-label" aria-describedby="node-user-name-value"&gt;
      &lt;span&gt;
        &lt;span id="node-user-name-label"&gt;name&lt;/span&gt;: &lt;span id="node-user-name-value"&gt;"Alice"&lt;/span&gt; &lt;!-- Value --&gt;
      &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;

&lt;li role="treeitem" aria-level="1" aria-posinset="2" aria-setsize="2"
    id="node-items" aria-labelledby="node-items-label" aria-describedby="node-items-value" aria-expanded="false"&gt;
  &lt;span&gt;
    &lt;span id="node-items-label"&gt;items&lt;/span&gt;: &lt;span id="node-items-value"&gt;[...]&lt;/span&gt; &lt;!-- Visually shows [...] --&gt;
    &lt;button aria-label="Expand items array"&gt;
        &lt;ChevronRight size={16} /&gt;
    &lt;/button&gt;
  &lt;/span&gt;
  &lt;ul role="group" className="hidden"&gt;
    &lt;li role="treeitem" aria-level="2" aria-posinset="1" aria-setsize="2"
        id="node-items-0" aria-labelledby="node-items-0-label" aria-describedby="node-items-0-value"&gt;
      &lt;span&gt;
        &lt;span id="node-items-0-label"&gt;[0]&lt;/span&gt;: &lt;span id="node-items-0-value"&gt;"fiction"&lt;/span&gt;
      &lt;/span&gt;
    &lt;/li&gt;
    &lt;li role="treeitem" aria-level="2" aria-posinset="2" aria-setsize="2"
        id="node-items-1" aria-labelledby="node-items-1-label" aria-describedby="node-items-1-value"&gt;
      &lt;span&gt;
        &lt;span id="node-items-1-label"&gt;[1]&lt;/span&gt;: &lt;span id="node-items-1-value"&gt;false&lt;/span&gt;
      &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;

Assigning unique IDs to the key/index and value elements allows them to be referenced by aria-labelledby and aria-describedby on the containing treeitem. This provides a rich, semantic description to assistive technologies.

aria-activedescendant

This attribute is typically placed on the element with role="tree" (the tree container) when implementing ARIA Focussed Container pattern for keyboard navigation. Instead of moving the actual browser focus to each treeitem as the user navigates with arrow keys, the focus stays on the container, and aria-activedescendant is updated to the ID of the currently "active" (virtually focused) treeitem. Assistive technologies monitor this attribute to announce the correct item.

Example: aria-activedescendant

&lt;ul role="tree" aria-labelledby="json-tree-label" aria-activedescendant="node-user-name"&gt;
  &lt;li role="treeitem" aria-level="1" aria-posinset="1" aria-setsize="2" id="node-user" ...&gt;...&lt;/li&gt;
  &lt;li role="treeitem" aria-level="1" aria-posinset="2" aria-setsize="2" id="node-items" ...&gt;
    &lt;ul role="group"&gt;
      &lt;li role="treeitem" aria-level="2" aria-posinset="1" aria-setsize="2" id="node-items-0" ...&gt;...&lt;/li&gt;
      &lt;li role="treeitem" aria-level="2" aria-posinset="2" aria-setsize="2" id="node-items-1" ...&gt;...&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;!-- In this state, the tree container element has browser focus,
     but assistive technologies treat "node-items-1" as the active item --&gt;

Implementing this pattern requires careful management of keyboard events (Up/Down/Left/Right arrow keys) and updating the aria-activedescendant attribute on the tree root.

Keyboard Interaction

While ARIA provides the semantic meaning, a truly accessible tree view must also implement standard keyboard navigation. The expected keyboard shortcuts for tree views include:

  • Arrow Up / Arrow Down: Move focus/active descendant to the previous/next visible treeitem.
  • Arrow Right:
    • When focus is on a closed node, opens the node (sets aria-expanded="true").
    • When focus is on an open node, moves focus to the first child node.
    • When focus is on a leaf node, does nothing.
  • Arrow Left:
    • When focus is on an open node, closes the node (sets aria-expanded="false").
    • When focus is on a closed node or a leaf node, moves focus to the parent node.
  • Home: Moves focus to the first treeitem in the tree.
  • End: Moves focus to the last visible treeitem in the tree.
  • Enter or Space: Performs the default action for the node (e.g., selecting it, if selection is supported).

Implementing this complex keyboard navigation requires JavaScript, but the ARIA attributes ensure that assistive technologies understand what is happening as the user navigates.

Putting it Together: A Full Example Snippet

This snippet illustrates a simplified structure for a JSON object with nested properties and an array, incorporating the ARIA attributes discussed.

Example: Structure for {"name": "Alice", "age": 30, "address": {"city": "wonderland"}, "tags": ["fiction", "adventure"]}

&lt;div&gt; &lt;!-- Container for label and tree --&gt;
  &lt;span id="json-tree-label" className="sr-only"&gt;User Data JSON&lt;/span&gt;

  &lt;ul role="tree" aria-labelledby="json-tree-label" tabindex="0"&gt; &lt;!-- tabindex="0" makes the tree focusable --&gt;

    &lt;li role="treeitem" aria-level="1" aria-posinset="1" aria-setsize="1"
        id="node-root" aria-labelledby="node-root-label" aria-describedby="node-root-value" aria-expanded="true"&gt;
      &lt;!-- The node label/value display area --&gt;
      &lt;span&gt;
        &lt;span id="node-root-label"&gt;root&lt;/span&gt;: &lt;span id="node-root-value"&gt;&#x7b;&#x7d;&lt;/span&gt; &lt;!-- Visual representation of the object --&gt;
        &lt;!-- Button to toggle expansion --&gt;
        &lt;button aria-label="Collapse root object"&gt;
            &lt;ChevronDown size={16} /&gt;
        &lt;/button&gt;
      &lt;/span&gt;

      &lt;!-- Group for children --&gt;
      &lt;ul role="group"&gt;

        &lt;!-- "name": "Alice" --&gt;
        &lt;li role="treeitem" aria-level="2" aria-posinset="1" aria-setsize="4"
            id="node-name" aria-labelledby="node-name-label" aria-describedby="node-name-value"&gt;
          &lt;span&gt;
            &lt;span id="node-name-label"&gt;name&lt;/span&gt;: &lt;span id="node-name-value"&gt;"Alice"&lt;/span&gt;
          &lt;/span&gt;
        &lt;/li&gt;

        &lt;!-- "age": 30 --&gt;
        &lt;li role="treeitem" aria-level="2" aria-posinset="2" aria-setsize="4"
            id="node-age" aria-labelledby="node-age-label" aria-describedby="node-age-value"&gt;
          &lt;span&gt;
            &lt;span id="node-age-label"&gt;age&lt;/span&gt;: &lt;span id="node-age-value"&gt;30&lt;/span&gt;
          &lt;/span&gt;
        &lt;/li&gt;

        &lt;!-- "address": &#x7b;...&#x7d; (Expandable) --&gt;
        &lt;li role="treeitem" aria-level="2" aria-posinset="3" aria-setsize="4"
            id="node-address" aria-labelledby="node-address-label" aria-describedby="node-address-value" aria-expanded="true"&gt;
          &lt;span&gt;
            &lt;span id="node-address-label"&gt;address&lt;/span&gt;: &lt;span id="node-address-value"&gt;&#x7b;&#x7d;&lt;/span&gt;
            &lt;button aria-label="Collapse address object"&gt;
                 &lt;ChevronDown size={16} /&gt;
            &lt;/button&gt;
          &lt;/span&gt;
          &lt;ul role="group"&gt;
            &lt;!-- "city": "wonderland" --&gt;
            &lt;li role="treeitem" aria-level="3" aria-posinset="1" aria-setsize="1"
                id="node-address-city" aria-labelledby="node-address-city-label" aria-describedby="node-address-city-value"&gt;
              &lt;span&gt;
                &lt;span id="node-address-city-label"&gt;city&lt;/span&gt;: &lt;span id="node-address-city-value"&gt;"wonderland"&lt;/span&gt;
              &lt;/span&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/li&gt;

        &lt;!-- "tags": [...] (Expandable Array) --&gt;
        &lt;li role="treeitem" aria-level="2" aria-posinset="4" aria-setsize="4"
            id="node-tags" aria-labelledby="node-tags-label" aria-describedby="node-tags-value" aria-expanded="false"&gt;
          &lt;span&gt;
            &lt;span id="node-tags-label"&gt;tags&lt;/span&gt;: &lt;span id="node-tags-value"&gt;[...]&lt;/span&gt;
            &lt;button aria-label="Expand tags array"&gt;
                &lt;ChevronRight size={16} /&gt;
            &lt;/button&gt;
          &lt;/span&gt;
          &lt;ul role="group" className="hidden"&gt;
            &lt;!-- "fiction" --&gt;
            &lt;li role="treeitem" aria-level="3" aria-posinset="1" aria-setsize="2"
                id="node-tags-0" aria-labelledby="node-tags-0-label" aria-describedby="node-tags-0-value"&gt;
              &lt;span&gt;
                 &lt;span id="node-tags-0-label"&gt;[0]&lt;/span&gt;: &lt;span id="node-tags-0-value"&gt;"fiction"&lt;/span&gt;
              &lt;/span&gt;
            &lt;/li&gt;
            &lt;!-- "adventure" --&gt;
            &lt;li role="treeitem" aria-level="3" aria-posinset="2" aria-setsize="2"
                id="node-tags-1" aria-labelledby="node-tags-1-label" aria-describedby="node-tags-1-value"&gt;
              &lt;span&gt;
                 &lt;span id="node-tags-1-label"&gt;[1]&lt;/span&gt;: &lt;span id="node-tags-1-value"&gt;"adventure"&lt;/span&gt;
              &lt;/span&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/li&gt;

      &lt;/ul&gt; &lt;!-- End root group --&gt;
    &lt;/li&gt; &lt;!-- End root treeitem --&gt;

  &lt;/ul&gt; &lt;!-- End tree --&gt;
&lt;/div&gt;

This example shows how roles, levels, position/set size, and labeling/description attributes work together across different types of JSON nodes (object, key-value, array, array item). Note the use of unique IDs for each node and its label/value parts.

Testing Your Accessible Tree View

Implementing ARIA attributes is crucial, but verifying the accessibility is equally important.

  • Keyboard Navigation: Try navigating the tree view using only the keyboard (arrow keys, Home, End, Enter). Ensure that focus moves correctly and nodes expand/collapse as expected.
  • Screen Reader Testing: Use a screen reader (e.g., VoiceOver on macOS, NVDA on Windows, JAWS) to interact with the tree view. Listen to what is announced when navigating, expanding, and collapsing nodes. Does it correctly announce the role ("tree", "tree item", "group"), the level, position in set, and the label/description of the node?
  • Automated Tools: Use browser extensions (like axe DevTools, Lighthouse) or online validators to check for common ARIA usage errors.
  • Manual Code Review: Double-check that ARIA attributes are correctly applied according to the WAI-ARIA Tree View pattern. Ensure IDs used in aria-labelledby and aria-describedby correctly point to the intended elements and are unique.

Conclusion

Creating accessible interactive components like JSON tree views is essential for building inclusive web applications. By applying the appropriate ARIA roles (tree, treeitem, group) and states/properties (aria-expanded, aria-selected, aria-level, aria-posinset, aria-setsize, aria-labelledby, aria-describedby, aria-activedescendant), along with robust keyboard navigation, you can ensure that users of assistive technologies have a semantic understanding and navigable experience comparable to visual users. Implementing these patterns requires careful attention to detail but is a fundamental step towards building truly accessible user interfaces.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool