Beta Preview

TreeView

A tree view provides users with a way to navigate nested hierarchical information.

selectionMode 
isDetached 
isEmphasized 
import {TreeView, TreeViewItem, TreeViewItemContent} from '@react-spectrum/s2';

<TreeView
  aria-label="Files"
  selectionMode="multiple"
  defaultExpandedKeys={['documents']}>
  <TreeViewItem id="documents" textValue="Documents">
    <TreeViewItemContent>Documents</TreeViewItemContent>
    <TreeViewItem id="project-a" textValue="Project A">
      <TreeViewItemContent>Project A</TreeViewItemContent>
      <TreeViewItem id="report" textValue="Weekly Report">
        <TreeViewItemContent>Weekly Report</TreeViewItemContent>
      </TreeViewItem>
    </TreeViewItem>
    <TreeViewItem id="readme" textValue="README">
      <TreeViewItemContent>README</TreeViewItemContent>
    </TreeViewItem>
  </TreeViewItem>
</TreeView>

Content

TreeView follows the Collection Components API, accepting both static and dynamic collections. This example shows a dynamic collection, passing a list of objects to the items prop, and a recursive function to render the children.

import {TreeView, TreeViewItem, TreeViewItemContent, Collection} from '@react-spectrum/s2';

<TreeView aria-label="Files" defaultExpandedKeys={[1, 4]} items={items} selectionMode="multiple"> {function renderItem(item) { return (
<TreeViewItem> <TreeViewItemContent>{item.title}</TreeViewItemContent> {/* recursively render children */} <Collection items={item.children}> {renderItem} </Collection> </TreeViewItem> ); }} </TreeView>

Slots

TreeViewItemContent supports icons, Text, ActionMenu, and ActionButtonGroup as children.

import {TreeView, TreeViewItem, TreeViewItemContent, Collection, ActionMenu, MenuItem, Text} from '@react-spectrum/s2';
import Folder from '@react-spectrum/s2/icons/Folder';
import File from '@react-spectrum/s2/icons/File';
import Edit from '@react-spectrum/s2/icons/Edit';
import Delete from '@react-spectrum/s2/icons/Delete';

<TreeView aria-label="Files" defaultExpandedKeys={[1, 4]} items={items} selectionMode="multiple"> {function renderItem(item) { return (
<TreeViewItem> <TreeViewItemContent> {item.type === 'directory' ? <Folder /> : <File />} <Text>{item.title}</Text> <ActionMenu> <MenuItem> <Edit /> <Text>Edit</Text> </MenuItem> <MenuItem> <Delete /> <Text>Delete</Text> </MenuItem> </ActionMenu> </TreeViewItemContent> <Collection items={item.children}> {renderItem} </Collection> </TreeViewItem> ); }} </TreeView>

Asynchronous loading

Use renderEmptyState to display a spinner during initial load. To enable infinite scrolling, render a <TreeViewLoadMoreItem> at the end of each <TreeViewItem>.

import {TreeView, TreeViewItem, TreeViewItemContent, TreeViewLoadMoreItem, Collection} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {useAsyncList} from 'react-stately';

interface Character {
  name: string
}

function AsyncLoadingExample() {
return ( <TreeView aria-label="Async loading tree" styles={style({height: 300})}> <TreeViewItem> <TreeViewItemContent>Pokemon</TreeViewItemContent> <Collection items={pokemonList.items}> {(item) => ( <TreeViewItem id={item.name}> <TreeViewItemContent>{item.name}</TreeViewItemContent> </TreeViewItem>
)} </Collection> <TreeViewLoadMoreItem onLoadMore={pokemonList.loadMore} isLoading={pokemonList.loadingState === 'loadingMore'} /> </TreeViewItem> <TreeViewItem> <TreeViewItemContent>Star Wars</TreeViewItemContent> <Collection items={starWarsList.items}> {(item) => ( <TreeViewItem id={item.name}> <TreeViewItemContent>{item.name}</TreeViewItemContent> </TreeViewItem> )} </Collection> <TreeViewLoadMoreItem onLoadMore={starWarsList.loadMore} isLoading={starWarsList.loadingState === 'loadingMore'} /> </TreeViewItem> </TreeView> ); }

Use the href prop on a <TreeItem> to create a link. See the client side routing guide to learn how to integrate with your framework. See the selection guide for more details.

import {TreeView, TreeViewItem, TreeViewItemContent} from '@react-spectrum/s2';

<TreeView
  aria-label="TreeView with links"
  selectionMode="multiple"
  defaultExpandedKeys={['bulbasaur', 'ivysaur']}>
  <TreeViewItem
    href="https://pokemondb.net/pokedex/bulbasaur"
    target="_blank"
    id="bulbasaur">
    <TreeViewItemContent>Bulbasaur</TreeViewItemContent>
    <TreeViewItem
      id="ivysaur"
      href="https://pokemondb.net/pokedex/ivysaur"
      target="_blank">
      <TreeViewItemContent>Ivysaur</TreeViewItemContent>
      <TreeViewItem
        id="venusaur"
        title="Venusaur"
        href="https://pokemondb.net/pokedex/venusaur"
        target="_blank">
        <TreeViewItemContent>Venusaur</TreeViewItemContent>
      </TreeViewItem>
    </TreeViewItem>
  </TreeViewItem>
</TreeView>

Empty state

Use renderEmptyState to render placeholder content when the tree is empty.

import {TreeView, IllustratedMessage, Heading, Content, Link} from '@react-spectrum/s2';
import FolderOpen from '@react-spectrum/s2/illustrations/linear/FolderOpen';

<TreeView
  aria-label="Search results"
  renderEmptyState={() => (
    <IllustratedMessage>
      <FolderOpen />
      <Heading>No results</Heading>
      <Content>Press <Link href="https://adobe.com">here</Link> for more info.</Content>
    </IllustratedMessage>
  )}>
  {[]}
</TreeView>

Selection and actions

Use the selectionMode prop to enable single or multiple selection. The selected items can be controlled via the selectedKeys prop, matching the id prop of the items. The onAction event handles item actions. Items can be disabled with the isDisabled prop. See the selection guide for more details.

Current selection:

selectionMode 
disabledBehavior 
disallowEmptySelection 
import {TreeView, TreeViewItem, TreeViewItemContent, type Selection} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {useState} from 'react';

function Example(props) {
  let [selected, setSelected] = useState<Selection>(new Set());

  return (
    <div className={style({width: 'full'})}>
      <TreeView
        {...props}
        aria-label="Pokemon evolution"
        styles={style({height: 250, width: 'full', maxWidth: 300})}
        selectionMode="multiple"
        selectedKeys={selected}
        onSelectionChange={setSelected}
        onAction={key => alert(`Clicked ${key}`)}
      >
        <TreeViewItem id="bulbasaur">
          <TreeViewItemContent>Bulbasaur</TreeViewItemContent>
          <TreeViewItem id="ivysaur">
            <TreeViewItemContent>Ivysaur</TreeViewItemContent>
            <TreeViewItem id="venusaur" isDisabled>
              <TreeViewItemContent>Venusaur</TreeViewItemContent>
            </TreeViewItem>
          </TreeViewItem>
        </TreeViewItem>
        <TreeViewItem id="charmander">
          <TreeViewItemContent>Charmander</TreeViewItemContent>
          <TreeViewItem id="charmeleon">
            <TreeViewItemContent>Charmeleon</TreeViewItemContent>
            <TreeViewItem id="charizard">
              <TreeViewItemContent>Charizard</TreeViewItemContent>
            </TreeViewItem>
          </TreeViewItem>
        </TreeViewItem>
        <TreeViewItem id="squirtle">
          <TreeViewItemContent>Squirtle</TreeViewItemContent>
          <TreeViewItem id="wartortle">
            <TreeViewItemContent>Wartortle</TreeViewItemContent>
            <TreeViewItem id="blastoise">
              <TreeViewItemContent>Blastoise</TreeViewItemContent>
            </TreeViewItem>
          </TreeViewItem>
        </TreeViewItem>
      </TreeView>
      <p>Current selection: {selected === 'all' ? 'all' : [...selected].join(', ')}</p>
    </div>
  );
}

API

<TreeView>
  <TreeViewItem>
    <TreeViewItemContent>
      <Icon />
      <Text />
      <ActionMenu /> or <ActionButtonGroup />
    </TreeViewItemContent>
    <TreeViewItem>
      {/* ... */}
    </TreeViewItem>
    <TreeViewLoadMoreItem />
  </TreeViewItem>
</TreeView>

TreeView

NameType
styles
Spectrum-defined styles, returned by the style() macro.
expandedKeysIterable<Key>
The currently expanded keys in the collection (controlled).
defaultExpandedKeysIterable<Key>
The initial expanded keys in the collection (uncontrolled).
isDetachedboolean
Whether the tree should be displayed with a detached style.
isEmphasizedboolean
Whether the tree should be displayed with a emphasized style.
childrenReactNode(item: T) => ReactNode
The contents of the collection.
itemsIterable<T>
Item objects in the collection.
renderEmptyState(props: ) => ReactNode
Provides content to display when there are no items in the list.
dependenciesReadonlyArray<any>
Values that should invalidate the item cache when using dynamic collections.
selectionMode
The type of selection that is allowed in the collection.
selectedKeys'all'Iterable<Key>
The currently selected keys in the collection (controlled).
defaultSelectedKeys'all'Iterable<Key>
The initial selected keys in the collection (uncontrolled).
onSelectionChange(keys: ) => void
Handler that is called when the selection changes.
disabledKeysIterable<Key>
The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
disabledBehavior
Whether disabledKeys applies to all interactions, or only selection.
disallowEmptySelectionboolean
Whether the collection allows empty selection.
shouldSelectOnPressUpboolean
Whether selection should occur on press up instead of press down.
escapeKeyBehavior'clearSelection''none'
Whether pressing the escape key should clear selection in the grid list or not. Most experiences should not modify this option as it eliminates a keyboard user's ability to easily clear selection. Only use if the escape key is being handled externally or should not trigger selection clearing contextually.

TreeViewItem

NameType
hasChildItemsboolean
Whether this item has children, even if not loaded yet.
idKey
The unique id of the tree row.
valueT
The object value that this tree item represents. When using dynamic collections, this is set automatically.
textValuestring
A string representation of the tree item's contents, used for features like typeahead.
childrenReactNode
The content of the tree item along with any nested children. Supports static nested tree items or use of a Collection to dynamically render nested tree items.
isDisabledboolean
Whether the item is disabled.

TreeViewItemContent

NameType
childrenReactNode
Rendered contents of the tree item or child items.

TreeViewLoadMoreItem

NameType
loadingState
The current loading state of the TreeView or TreeView row.
onLoadMore() => any
Handler that is called when more items should be loaded, e.g. while scrolling near the bottom.

Testing

General setup

TreeView features long press interactions on its rows depending on the row actions provided and if the user is interacting with the tree on a touch device. Please see the following sections in the general testing documentation for more information on how to handle these behaviors in your test suite.

Timers

Long press

Test utils

@react-spectrum/test-utils offers common tree interaction utilities which you may find helpful when writing tests. To install, simply add it to your dev dependencies via your preferred package manager.

yarn add @react-spectrum/test-utils --dev

Once installed, you can access the User that @react-spectrum/test-utils provides in your test file as shown below. This user only needs to be initialized once and then can be used to generate the TreeView tester in your test cases. This gives you access to TreeView specific utilities that you can then call within your test to query for specific subcomponents or simulate common interactions. The example test case below shows how you might go about setting up the TreeView tester, use it to simulate row expansion and selection, and verify the tree's state after each interaction.

// Tree.test.ts
import {render, within} from '@testing-library/react';
import {User} from '@react-spectrum/test-utils';

let testUtilUser = new User({interactionType: 'mouse'});
// ...

it('TreeView can select a item via keyboard', async function () {
  // Render your test component/app and initialize the Tree tester
  let {getByTestId} = render(
    <Tree data-testid="test-tree" selectionMode="multiple">
      ...
    </Tree>
  );
  let treeTester = testUtilUser.createTester('Tree', {root: getByTestId('test-tree'), interactionType: 'keyboard'});

  await treeTester.toggleRowSelection({row: 0});
  expect(treeTester.selectedRows).toHaveLength(1);
  expect(within(treeTester.rows[0]).getByRole('checkbox')).toBeChecked();

  await treeTester.toggleRowSelection({row: 1});
  expect(treeTester.selectedRows).toHaveLength(2);
  expect(within(treeTester.rows[1]).getByRole('checkbox')).toBeChecked();

  await treeTester.toggleRowSelection({row: 0});
  expect(treeTester.selectedRows).toHaveLength(1);
  expect(within(treeTester.rows[0]).getByRole('checkbox')).not.toBeChecked();

  await treeTester.toggleRowExpansion({index: 0});
  expect(treeTester.rows[0]).toHaveAttribute('aria-expanded', 'true');
});

See below for the full definition of the User and the TreeView tester.

Properties

NameTypeDefault
advanceTimer['advanceTimer']Default:
A function used by the test utils to advance timers during interactions. Required for certain aria patterns (e.g. table).
interactionType['interactionType']Default: mouse
The interaction type (mouse, touch, keyboard) that the test util user will use when interacting with a component. This can be overridden at the aria pattern util level if needed.

Methods

constructor(opts: ): void
createTester<T extends >(patternName: T, opts: <T>): <T>
Creates an aria pattern tester, inheriting the options provided to the original user.

Properties

NameType
selectedRowsHTMLElement[]
Returns the tree's selected rows if any.
rowsHTMLElement[]
Returns the tree's rows if any.
treeHTMLElement
Returns the tree.

Methods

constructor(opts: ): void
setInteractionType(type: ['interactionType']): void
Set the interaction type used by the tree tester.
findRow(opts: {
rowIndexOrText: numberstring
}): HTMLElement
Returns a row matching the specified index or text content.
toggleRowSelection(opts: ): Promise<void>
Toggles the selection for the specified tree row. Defaults to using the interaction type set on the tree tester. Note that this will endevor to always add/remove JUST the provided row to the set of selected rows.
toggleRowExpansion(opts: ): Promise<void>
Toggles the expansion for the specified tree row. Defaults to using the interaction type set on the tree tester.
triggerRowAction(opts: ): Promise<void>
Triggers the action for the specified tree row. Defaults to using the interaction type set on the tree tester.
cells(opts: {
element?: HTMLElement
}): HTMLElement[]
Returns the tree's cells if any. Can be filtered against a specific row if provided via element.

Testing FAQ