Tree

The tree organizes data into a tree-like structure with collapsible and expandable nodes. It allows users to navigate complex hierarchical information, such as directories and categories. The component typically includes parent and child nodes, where parent nodes can be expanded to reveal their children.

UsageCodeAccessibility
<sl-tree id="tree" aria-label="Subjects structure"></sl-tree>

<script type="module">
import { FlatTreeDataSource } from '@sl-design-system/tree';

const tree = document.querySelector('#tree');

  const flatData = [
    { id: 0, expandable: true, level: 0, name: 'Mathematics' },
    { id: 1, expandable: true, level: 1, name: 'Algebra' },
    { id: 2, expandable: false, level: 2, name: 'Lesson 1 - Linear equations.md' },
    { id: 3, expandable: false, level: 2, name: 'Lesson 2 - Quadratic equations.md' },
    { id: 4, expandable: true, level: 1, name: 'Geometry' },
    ...
    { id: 18, expandable: true, level: 1, name: 'Modern History' },
    { id: 19, expandable: false, level: 2, name: 'World War I.md' },
    { id: 20, expandable: false, level: 2, name: 'World War II.md' }
  ];

  const dataSource = new FlatTreeDataSource(flatData, {
    getIcon: ({ name }, expanded) =>
      name.includes('.') ? 'far-file-lines' : `far-folder${expanded ? '-open' : ''}`,
    getId: (item) => item.id,
    getLabel: ({ name }) => name,
    getLevel: ({ level }) => level,
    isExpandable: ({ expandable }) => expandable,
    isExpanded: ({ name }) => ['tree', 'src'].includes(name),
    selects: 'multiple'
  });

  tree.dataSource = dataSource;
</script>

When to use

The following guidance describes when to use the tree component.

Hierarchical Content

The Tree is ideal for displaying and navigating hierarchical data, such as file systems or category structures. It groups related data with nested relationships in a clear and organised way. It is a powerful tool for organising subject lesson content in educational applications. For example, it represents a syllabus with modules as parent nodes and lessons as child nodes, enabling students to intuitively explore and navigate their coursework.

Collapsable Data

The Tree allows users to dynamically expand or collapse data sections, enabling a cleaner interface that focuses only on relevant information. This interactive functionality is handy when managing large amounts of content, where each item contains complex data or detailed information. It ensures users can explore deeply without being overwhelmed.

When not to use

Do not use Tree as the primary navigation for your product’s UI. The tree component is not suitable for this purpose.

Elements Toggle

The tree is not ideal for showing and hiding UI elements or content within a page. If the interaction only involves collapsing or expanding content at a basic level, consider using other components like accordions or collapsable panels for a more straightforward solution.

Anatomy

ItemNameDescriptionOptional
1TitleThe main text label of the node, representing the name or identifier of the item in the hierarchy.no
2ToggleAn interactive element that allows users to expand or collapse child nodes under the parent node.Yes
3IconA visual indicator representing the type or state of the node.Yes
4CheckboxA selectable option that allows users to select or deselect nodes.Yes
5Guide LinesVisual lines used to indicate the hierarchical structure of the tree.Yes
6IndentationThe visual offset of child nodes to indicate their relationship with parent nodes.Yes

Variants

Tree comes in three versions, each suited for specific situations:

Node

A basic element in the tree, representing a terminal item without parent or child nodes.

Parent Node

A node that has one or more child nodes, typically expandable to reveal more detailed data.

Child Node

A node nested under a parent node, typically hidden until the parent is expanded, representing more detailed or lower-level information.

Figma Options

With these options, you can tweak the appearance of the Tree in Figma. They are available in the Design Panel so you can compose the switch to exactly fit the user experience need for the use case you are working on.

ItemOptionsDescription
Variant'Child', 'Parent'Defines whether the node behaves as a parent with expandable content or a child item.
Expanded'on', 'off'Toggles whether the parent node is expanded to show its children.
Multiselect'on', 'off'Enables the ability to select multiple nodes at once.
Selected'on', 'off'Indicates whether the node is currently selected.
Node LabelTextThe text label displayed as the node’s title.
Icon'on', 'off'Shows or hides the icon associated with the node (e.g., folder, file).
showFocus'on', 'off'Displays a focus ring around the node when active.
Level'1' to '6'Sets the hierarchy level to control indentation.
hideGuides'on', 'off'Shows or hides the connector guides between parent and child nodes.
Actions Type'Badge', 'Button Bar'Toggles additional actions available on the node (e.g., edit, delete).
State'idle', 'hover', 'active'Represents the interactive state of the node (e.g., default, hover).
Selected'on', 'off'Indicates if the node is selected in its current state.

Behaviour

Let's explore the behaviour of the Tree.

Selectable

When this feature is enabled, nodes can be selected by the user, allowing for interactions such as checking, highlighting, or performing actions on a specific node.

Multiple Selection

When this feature is enabled, users can select multiple nodes at the same time, allowing interactions such as checking, highlighting, or performing actions on several nodes simultaneously. Single selection is disabled in this mode to simplify user interaction.

Expandable

Allowing users to click on a parent node to reveal its child nodes. This interaction helps in navigating deeper structures without overwhelming the user with too much data at once. It is essential for managing large hierarchical datasets.

Indentation

Indentation visually distinguishes parent nodes from their child nodes by shifting them to the right. This hierarchy helps users understand the relationship between different levels of the data quickly.

<sl-tree aria-label="Files structure"></sl-tree>

<script type="module">
import { html, nothing } from 'lit';
import { FlatTreeDataSource } from '@sl-design-system/tree';

const flatData = [
  { id: 0, expandable: true, level: 0, name: 'textarea' },
  { id: 1, expandable: false, level: 1, name: 'package.json' },
  { id: 2, expandable: true, level: 0, name: 'tree' },
  { id: 3, expandable: true, level: 1, name: 'src' },
  { id: 4, expandable: false, level: 2, name: 'tree-node.ts' }
];

const dataSource = new FlatTreeDataSource(flatData, {
  getIcon: ({ name }, expanded) =>
    name.includes('.') ? 'far-file-lines' : `far-folder${expanded ? '-open' : ''}`,
  getId: (item) => item.id,
  getLabel: ({ name }) => name,
  getLevel: ({ level }) => level,
  isExpandable: ({ expandable }) => expandable,
  isExpanded: ({ name }) => ['tree', 'src'].includes(name),
  selects: 'single'
});

const renderer = (node) => {
  const icon = node.label.includes('.') ? 'far-file-lines' : `far-folder${node.expanded ? '-open' : ''}`;

  const onClickEdit = (event) => {
    event.stopPropagation();
  };

  const onClickRemove = (event) => {
    event.stopPropagation();
  };

  return html`
      ${icon ? html`<sl-icon size="sm" .name=${icon}></sl-icon>` : nothing}
      <span>${node.label}</span>

      <sl-button fill="ghost" size="sm" slot="actions" @click=${onClickEdit} aria-label="Edit">
        <sl-icon name="far-pen"></sl-icon>
      </sl-button>
      <sl-button fill="ghost" size="sm" slot="actions" @click=${onClickRemove} aria-label="Remove">
        <sl-icon name="far-trash"></sl-icon>
      </sl-button>
    `;
}

const tree = document.querySelector('sl-tree');
tree.dataSource = dataSource;
tree.renderer = renderer;
</script>

Data source and custom rendering

The tree component requires a data source to supply structure and manage state. This component provides FlatTreeDataSource, which adapts a flat array to a hierarchical view, and NestedTreeDataSource, which works directly with nested tree-structured data.

What is a data source?

In the Sanoma Learning Design System, a data source is a small adapter that normalizes your raw data into the view model that UI components use. It centralizes filtering, sorting, selection, and emits sl-update when its view changes. Multiple components (e.g. tree, grid, paginator) share the same base DataSource so behaviour stays consistent. For tree, FlatTreeDataSource extends the base and maps a flat array with a level field into a hierarchical view while tracking expansion and selection. Alternatively, you can use NestedTreeDataSource if your data is already structured as a nested tree, allowing you to work directly with hierarchical data without flattening it first.

A data source is the adapter that supplies the tree with items, their stable ids, labels, hierarchy (via level), expandability, and optional initial expansion/selection. The tree reads from it to render nodes and maintain expansion/selection and keyboard behavior consistently. More information about the data source used in the tree you can find below.

What is a renderer function?

The tree component supports custom rendering of nodes via a renderer function. This allows you to add custom icons, buttons, or other interactive elements to each node.

Use a renderer to customize each node's content while preserving built-in selection, focus, and keyboard behavior. The function receives the node's render data (id, label, expanded, level, and selection state) and must return a TemplateResult or Node or DocumentFragment. More information about the renderer function is below. More information about the rendered function you can find below.

Data source reference

The tree reads items from a data source to determine identity, labels, hierarchy, expandability, and initial expansion. This component ships with two data sources:

  • FlatTreeDataSource for flat arrays that encode hierarchy via a level property.
  • NestedTreeDataSource for data already structured as a nested tree (children as arrays).

FlatTreeDataSource

  • Constructor
    • new FlatTreeDataSource(items, options)
  • Required callbacks on options
    • getId(item) → unique, stable id (string|number)
    • getLabel(item) → visible text
    • getLevel(item)number (0 for root)
    • isExpandable(item)boolean
  • Optional callbacks and flags
    • isExpanded(item)boolean initial expansion
    • getIcon(item, expanded) → icon name when not using a custom renderer
    • selects'single'|'multiple'|'none'
  • Methods
    • expandAll() and collapseAll() utility helpers

NestedTreeDataSource

  • Constructor
    • new NestedTreeDataSource(items, options)
  • Required callbacks on options
    • getId(item) → unique, stable id
    • getLabel(item) → visible text
    • getChildren(item) → array of children or undefined
  • Optional callbacks and flags
    • isExpanded(item)boolean initial expansion
    • getIcon(item, expanded) → icon name when not using a custom renderer
    • selects'single'|'multiple'|'none'
  • Methods
    • expandAll() and collapseAll() utility helpers

What the tree uses:

  • id to track focus, selection, and expansion state across updates.
  • label for rendering, typeahead, and accessible names.
  • level (for flat) or hierarchy (for nested) for indentation and aria-level.
  • isExpandable/getChildren to show/allow expansion.
  • isExpanded only for initial render; runtime interactions can change it.
  • getIcon only when no custom renderer provides its own icon.

Guidelines:

  • Keep id values stable between renders.
  • Ensure level (flat) or children (nested) reflect the visual order.
  • If you render inline actions, call e.stopPropagation() and add aria-labels (to icon-only buttons).
  • Prefer providing icons via renderer; when renderer is set, getIcon is ignored.

Renderer function

A renderer(node) function lets you set how each node looks while keeping the built-in selection, focus, and keyboard features working.

  • Function: renderer(node)Node|TemplateResult|DocumentFragment
  • Input node fields: id, label, level, expanded, and selection state
  • Output: a Node of TemplateResult or DocumentFragment that becomes the node’s content

What the tree uses:

  • Elements with slot="actions" are placed on the right as inline actions
  • When a renderer is provided, getIcon from the data source is ignored for that node

Guidelines:

  • Call e.stopPropagation() in inline action handlers to prevent unintended selection/expansion
  • Add accessible names with aria-label to icon-only controls
  • Do not mutate node; use tree APIs to change selection/expansion

Below you can find a tree example of a flat data source and a custom renderer function:


<sl-tree aria-label="Project files"></sl-tree>

<script type="module">
// Use FlatTreeDataSource
  // Flat data example
  const flatData = [
    { id: 0, expandable: true,  level: 0, name: 'docs' },
    { id: 1, expandable: false, level: 1, name: 'README.md' },
    { id: 2, expandable: true,  level: 0, name: 'src' },
    { id: 3, expandable: false, level: 1, name: 'index.ts' },
    { id: 4, expandable: false, level: 1, name: 'tree.ts' }
  ];

  // Build a data source and control expansion
  const dataSource = new FlatTreeDataSource(items, {
    getId: (item) => item.id,
    getLabel: (item) => item.name,
    getLevel: (item) => item.level, // 0-based depth in the flat list
    isExpandable: (item) => item.expandable,
    isExpanded: (item) => ['docs', 'src'].includes(item.name), // optional
    selects: 'multiple' // or 'single' | 'none'
  });
  
// or use NestedTreeDataSource for nested data:
  // Nested data example
  const nestedData = [
  {
    id: 0,
    name: 'docs',
    children: [
      { id: 1, name: 'README.md' }
    ]
  },
  {
    id: 2,
    name: 'src',
    children: [
      { id: 3, name: 'index.ts' },
      { id: 4, name: 'tree.ts' }
    ]
  }
  ];

  // Build a data source and control expansion
  const dataSource = new NestedTreeDataSource(nestedData, {
    getId: (item) => item.id,
    getLabel: (item) => item.name,
    getChildren: (item) => item.children,
    isExpanded: (item) => ['docs', 'src'].includes(item.name), // optional
    selects: 'multiple' // or 'single' | 'none'
  });

// Optional custom renderer for each node
const renderTreeNode = (renderData) => {
  const fragment = document.createDocumentFragment();

  const icon = document.createElement('sl-icon');
  icon.name = renderData.label.includes('.')
    ? 'far-file-lines'
    : `far-folder${renderData.expanded ? '-open' : ''}`;
  icon.setAttribute('size', 'sm');
  fragment.appendChild(icon);

  const label = document.createElement('span');
  label.textContent = renderData.label;
  fragment.appendChild(label);

  const renameButton = document.createElement('sl-button');
  renameButton.setAttribute('slot', 'actions');
  renameButton.setAttribute('fill', 'ghost');
  renameButton.setAttribute('size', 'sm');
  renameButton.setAttribute('aria-label', 'Rename');
  renameButton.addEventListener('click', (event) => event.stopPropagation());
  const renameIcon = document.createElement('sl-icon');
  renameIcon.name = 'far-pen';
  renameButton.appendChild(renameIcon);
  fragment.appendChild(renameButton);

  return fragment;
};

const treeElement = document.querySelector('sl-tree');
treeElement.dataSource = dataSource;
treeElement.renderer = renderTreeNode;
</script>

API

The tree API exposes a comprehensive set of properties, events, and customization options, enabling developers to tailor its behavior, appearance, and interaction patterns for diverse use cases.

Properties

NameAttributeTypeDefaultDescription
dataSource-TreeDataSource<T> | undefinedThe model for the tree.
hideGuideshide-guidesboolean | undefinedHides the indentation guides when set.
layoutComplete-Promise<void>Use this if you want to wait until lit-virtualizer has finished the rendering
the tree nodes. This can be useful in unit tests for example.
renderer-TreeItemRenderer<T> | undefinedCustom renderer function for tree items.
scopedElements-Record<string, typeof HTMLElement> | undefinedThe custom elements used for rendering this tree. If you are using a custom renderer
to render the tree nodes, any custom elements you use in the renderer need to be specified
via this property. Otherwise those custom elements will not initialize, since the tree
uses a Scoped Custom Element Registry.

Events

NameEvent typeDescription
sl-selectSlSelectEvent<TreeDataSourceNode<T>>Emits when the user selects a tree node.

Keyboard interactions

CommandDescription
TabMoves focus into the tree (to the first visible node) or to the next focusable element when leaving the tree. Inline action controls inside a node are tabbable.
Shift + TabMoves focus to the previous focusable element (can move focus out of the tree).
Arrow DownMoves focus to the next visible node.
Arrow UpMoves focus to the previous visible node.
Arrow RightOn a collapsed, expandable node: expands it and keeps focus. On an expanded node: moves focus to its first child.
Arrow LeftOn an expanded node: collapses it and keeps focus. On a leaf node (the nodes which don't have any child nodes are called leaf nodes): moves focus to its parent.
HomeMoves focus to the first visible node.
EndMoves focus to the last visible node.
Enter / SpaceSelects or toggles selection of the focused node.

WAI-ARIA

In the component itself we use multiple aria-attributes to assure the component works well with a range of assistive technologies. For some attributes however it is not possible for the Design System to add a meaningfull value, because it relies on the context or way a component is used.

Attributes that we recommend you add in certain scenarios are mentioned below.

Tree

AttributeValueDescription
aria-labelstringAccessible name for the sl-tree.
aria-labelledbystringReferences (via id) a visible element that labels the tree (e.g. a heading).

Tree node

AttributeValueDescription
aria-labelstringAccessible name for icon‑only buttons placed in slot="actions".
Interactive example