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.
<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
Global Navigation
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
Item | Name | Description | Optional |
---|---|---|---|
1 | Title | The main text label of the node, representing the name or identifier of the item in the hierarchy. | no |
2 | Toggle | An interactive element that allows users to expand or collapse child nodes under the parent node. | Yes |
3 | Icon | A visual indicator representing the type or state of the node. | Yes |
4 | Checkbox | A selectable option that allows users to select or deselect nodes. | Yes |
5 | Guide Lines | Visual lines used to indicate the hierarchical structure of the tree. | Yes |
6 | Indentation | The 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.
Item | Options | Description |
---|---|---|
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 Label | Text | The 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.
Related Components
<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 alevel
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 textgetLevel(item)
→number
(0
for root)isExpandable(item)
→boolean
- Optional callbacks and flags
isExpanded(item)
→boolean
initial expansiongetIcon(item, expanded)
→ icon name when not using a customrenderer
selects
→'single'
|'multiple'
|'none'
- Methods
expandAll()
andcollapseAll()
utility helpers
NestedTreeDataSource
- Constructor
new NestedTreeDataSource(items, options)
- Required callbacks on
options
getId(item)
→ unique, stable idgetLabel(item)
→ visible textgetChildren(item)
→ array of children orundefined
- Optional callbacks and flags
isExpanded(item)
→boolean
initial expansiongetIcon(item, expanded)
→ icon name when not using a customrenderer
selects
→'single'
|'multiple'
|'none'
- Methods
expandAll()
andcollapseAll()
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 andaria-level
.isExpandable
/getChildren
to show/allow expansion.isExpanded
only for initial render; runtime interactions can change it.getIcon
only when no customrenderer
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 addaria-label
s (to icon-only buttons). - Prefer providing icons via
renderer
; whenrenderer
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
ofTemplateResult
orDocumentFragment
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
Name | Attribute | Type | Default | Description |
---|---|---|---|---|
dataSource | - | TreeDataSource<T> | undefined | The model for the tree. | |
hideGuides | hide-guides | boolean | undefined | Hides 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> | undefined | Custom renderer function for tree items. | |
scopedElements | - | Record<string, typeof HTMLElement> | undefined | The 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
Name | Event type | Description |
---|---|---|
sl-select | SlSelectEvent<TreeDataSourceNode<T>> | Emits when the user selects a tree node. |
Keyboard interactions
Command | Description |
---|---|
Tab | Moves 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 + Tab | Moves focus to the previous focusable element (can move focus out of the tree). |
Arrow Down | Moves focus to the next visible node. |
Arrow Up | Moves focus to the previous visible node. |
Arrow Right | On a collapsed, expandable node: expands it and keeps focus. On an expanded node: moves focus to its first child. |
Arrow Left | On 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. |
Home | Moves focus to the first visible node. |
End | Moves focus to the last visible node. |
Enter / Space | Selects 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
Attribute | Value | Description |
---|---|---|
aria-label | string | Accessible name for the sl-tree . |
aria-labelledby | string | References (via id) a visible element that labels the tree (e.g. a heading). |
Tree node
Attribute | Value | Description |
---|---|---|
aria-label | string | Accessible name for icon‑only buttons placed in slot="actions" . |