Tree View
The TreeView component displays hierarchical data in an expandable/collapsible tree. It’s built from Tree, Folder, File, and an optional CollapseButton, and supports selection, status indicators, execution details, and RTL/LTR text direction.
Variations
Basic tree structure
Create a simple tree with folders and files. The tree automatically handles expand/collapse interactions.
With status indicators
The tree displays status icons based on the ExecutionState. It supports states like SUCCESS, RUNNING, FAILURE, PENDING, and more.
With initial selection
Specify an initially selected item with initialSelectedId. The tree automatically expands the parent folders to reveal it.
With initial expanded items
Control which folders start expanded with initialExpendedItems.
Non-selectable items
Make specific items non-selectable by setting isSelectable={false}.
With collapse button
Add an expand/collapse-all control with the CollapseButton component.
RTL support
The tree supports right-to-left text direction with the dir prop.
Full-featured pipeline execution tree
A comprehensive example combining multiple nested levels, various execution states, durations, initial selection and expansion, collapse/expand all, and a mix of selectable and non-selectable items in a real-world CI/CD structure.
Installation
import { Tree, Folder, File, CollapseButton, type TreeViewElement,} from "@harnessio/ui/components";import { ExecutionState } from "@harnessio/ui/types";
<Tree initialSelectedId="1-1" className="h-96 w-full rounded-cn-3 border"> <Folder element="Documents" value="folder-1" status={ExecutionState.SUCCESS} level={0} > <File value="1-1" status={ExecutionState.SUCCESS} level={1}> File 1 </File> </Folder></Tree>;Anatomy
A tree is composed of nested Folder and File nodes inside a Tree container.
Treeis the scrollable container and manages selection and expansion state for everything beneath it.Folderis an expandable branch. Itselementprop is the folder’s label (a string), and itschildrenare the nestedFolderandFilenodes it reveals when expanded. A child count is shown next to the label.Fileis a leaf node. Itschildrenare the file’s label text — not further nodes.- Every
FolderandFileneeds astatus(anExecutionState, which drives the status icon) and alevel(its nesting depth, which drives indentation). An optionaldurationstring renders on the right. CollapseButtonis an optional control that expands or collapses the whole tree; it needs the sameelementsarray so it knows what to toggle.
Rendering from data
When your tree comes from a data array, a small recursive helper keeps the markup manageable:
import { ReactNode } from "react";import { File, Folder, type TreeViewElement } from "@harnessio/ui/components";
interface RenderTreeElementProps { element: TreeViewElement; handleClick?: (params: { parentNode: TreeViewElement | null; childNode: TreeViewElement; }) => void; parentElement?: TreeViewElement | null; level?: number;}
// Main render functionexport const renderTree = ( elements: TreeViewElement[], handleClick?: (params: { parentNode: TreeViewElement | null; childNode: TreeViewElement; }) => void,): ReactNode => { if (elements.length === 0) return []; return elements.map((element) => ( <div key={element.id}> {renderTreeElement({ element, handleClick, parentElement: null })} </div> ));};
// Render folder with childrenconst renderTreeFolder = ({ element: folderElement, handleClick, level = 0,}: RenderTreeElementProps): ReactNode => { return ( <Folder element={folderElement.name} value={folderElement.id} status={folderElement.status} duration={folderElement.duration} isSelectable={folderElement.isSelectable} level={level} > {folderElement.children?.map((childElement) => ( <div key={childElement.id}> {renderTreeElement({ parentElement: folderElement, element: childElement, handleClick, level: level + 1, })} </div> ))} </Folder> );};
// Render file (leaf node)const renderTreeFile = ({ element: fileElement, handleClick, parentElement, level = 0,}: RenderTreeElementProps): ReactNode => { return ( <File value={fileElement.id} status={fileElement.status} duration={fileElement.duration} isSelectable={fileElement.isSelectable} handleSelect={() => handleClick?.({ parentNode: parentElement, childNode: fileElement }) } level={level} > {fileElement.name} </File> );};
// Decide whether to render folder or fileconst renderTreeElement = ({ element, handleClick, parentElement, level = 0,}: RenderTreeElementProps): ReactNode => { if (element.children && element.children.length > 0) { return renderTreeFolder({ element, handleClick, parentElement, level }); } return renderTreeFile({ element, handleClick, parentElement, level });};
// Usage<Tree className="h-96 w-full rounded-cn-3 border"> {renderTree(treeData, ({ parentNode, childNode }) => { console.log("Selected:", childNode.name); })}</Tree>;Best practices
Do
- Give every node a
statusso its state is visible at a glance, and a correctlevelso indentation matches the hierarchy. - Use
initialExpendedItemsto open the path a user cares about, andinitialSelectedIdto highlight it — the tree expands ancestors automatically for a selected id. - Set
isSelectable={false}on structural nodes (stages, groups) that users shouldn’t be able to select.
Don’t
- Don’t nest
Filenodes to represent branches — use aFolderfor anything with children. - Don’t hand-manage the
levelinconsistently; mismatched levels break the visual hierarchy.
Accessibility
Keyboard
- Folders are accordion triggers: Tab moves between them and Space or Enter toggles expansion.
- There is no up/down/left/right arrow-key navigation between nodes.
Labeling & semantics
- Selection is conveyed visually only; there is no
aria-selectedon the selected node. If selection state must be announced, pair the tree with your own live region. - File rows expose an accessible label, and the
CollapseButtonincludes visually-hidden “Toggle” text.
Properties
TreeViewElement
type TreeViewElement = { id: string; // Unique identifier for the tree item name: string; // Display name of the item isSelectable?: boolean; // Whether the item can be selected (default: true) children?: TreeViewElement[]; // Nested child elements status: ExecutionState; // Execution status (see ExecutionState enum) duration?: string; // Formatted duration string (e.g., "2m 30s")};ExecutionState
Imported from @harnessio/ui/types.
enum ExecutionState { PENDING = "pending", RUNNING = "running", SUCCESS = "success", FAILURE = "failure", ERROR = "error", SKIPPED = "skipped", KILLED = "killed", BLOCKED = "blocked", WAITING_ON_DEPENDENCIES = "waiting_on_dependencies", UNKNOWN = "unknown", FAILURE_IGNORED = "failure_ignored", IGNORE_FAILED = "ignorefailed",}Tree
Prop | Required | Default | Type |
|---|---|---|---|
| elements | false | TreeViewElement[] | |
| initialSelectedId | false | string | |
| initialExpendedItems | false | string[] | |
| indicator | false | true | boolean |
| dir | false | 'ltr' | 'rtl' | 'ltr' |
| className | false | string | |
| openIcon | false | React.ReactNode | |
| closeIcon | false | React.ReactNode |
Folder
Prop | Required | Default | Type |
|---|---|---|---|
| element | true | string | |
| value | true | string | |
| status | true | ExecutionState | |
| level | true | number | |
| duration | false | string | |
| isSelectable | false | true | boolean |
| isSelect | false | boolean |
File
Prop | Required | Default | Type |
|---|---|---|---|
| value | true | string | |
| children | true | React.ReactNode | |
| status | true | ExecutionState | |
| level | true | number | |
| duration | false | string | |
| isSelectable | false | true | boolean |
| isSelect | false | boolean | |
| handleSelect | false | (id: string) => void |
CollapseButton
Prop | Required | Default | Type |
|---|---|---|---|
| elements | true | TreeViewElement[] | |
| expandAll | false | false | boolean |
| children | false | React.ReactNode |