File System
node_modules
@skeletonlabs
skeleton
package.json
<script lang="ts">
import { FileIcon, FolderIcon } from '@lucide/svelte';
import { TreeView, createTreeViewCollection } from '@skeletonlabs/skeleton-svelte';
interface Node {
id: string;
name: string;
children?: Node[];
}
const collection = createTreeViewCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'root',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{
id: 'node_modules/@skeletonlabs',
name: '@skeletonlabs',
children: [
{
id: 'node_modules/@skeletonlabs/skeleton',
name: 'skeleton',
},
],
},
],
},
{
id: 'package.json',
name: 'package.json',
},
],
},
});
</script>
<TreeView {collection}>
<TreeView.Label>File System</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children || [] as node, index (node)}
{@render treeNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView>
{#snippet treeNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider value={{ node, indexPath }}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchIndicator />
<TreeView.BranchText>
<FolderIcon class="size-4" />
{node.name}
</TreeView.BranchText>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as childNode, childIndex (childNode)}
{@render treeNode(childNode, [...indexPath, childIndex])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<FileIcon class="size-4" />
{node.name}
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
File System
node_modules
@skeletonlabs
skeleton
package.json
import { TreeView, createTreeViewCollection } from '@skeletonlabs/skeleton-react';
import { FileIcon, FolderIcon } from 'lucide-react';
interface Node {
id: string;
name: string;
children?: Node[];
}
function TreeNode(props: { node: Node; indexPath: number[] }) {
return (
<TreeView.NodeProvider value={props}>
{props.node.children ? (
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchIndicator />
<TreeView.BranchText>
<FolderIcon className="size-4" />
{props.node.name}
</TreeView.BranchText>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{props.node.children.map((childNode, childIndex) => (
<TreeNode key={childNode.id} node={childNode} indexPath={[...props.indexPath, childIndex]} />
))}
</TreeView.BranchContent>
</TreeView.Branch>
) : (
<TreeView.Item>
<FileIcon className="size-4" />
{props.node.name}
</TreeView.Item>
)}
</TreeView.NodeProvider>
);
}
export default function Default() {
const collection = createTreeViewCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'root',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{
id: 'node_modules/@skeletonlabs',
name: '@skeletonlabs',
children: [
{
id: 'node_modules/@skeletonlabs/skeleton',
name: 'skeleton',
},
],
},
],
},
{
id: 'package.json',
name: 'package.json',
},
],
},
});
return (
<TreeView collection={collection}>
<TreeView.Label>File System</TreeView.Label>
<TreeView.Tree>
{collection.rootNode.children?.map((node, index) => (
<TreeNode node={node} indexPath={[index]} key={node.id} />
))}
</TreeView.Tree>
</TreeView>
);
}
Controlled
File System
node_modules
@skeletonlabs
skeleton
package.json
<script lang="ts">
import { FileIcon, FolderIcon } from '@lucide/svelte';
import { TreeView, createTreeViewCollection } from '@skeletonlabs/skeleton-svelte';
interface Node {
id: string;
name: string;
children?: Node[];
}
const collection = createTreeViewCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'root',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{
id: 'node_modules/@skeletonlabs',
name: '@skeletonlabs',
children: [
{
id: 'node_modules/@skeletonlabs/skeleton',
name: 'skeleton',
},
],
},
],
},
{
id: 'package.json',
name: 'package.json',
},
],
},
});
</script>
<TreeView {collection}>
<TreeView.Label>File System</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children || [] as node, index (node)}
{@render treeNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView>
{#snippet treeNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider value={{ node, indexPath }}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchIndicator />
<TreeView.BranchText>
<FolderIcon class="size-4" />
{node.name}
</TreeView.BranchText>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as childNode, childIndex (childNode)}
{@render treeNode(childNode, [...indexPath, childIndex])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<FileIcon class="size-4" />
{node.name}
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
File System
node_modules
@skeletonlabs
skeleton
package.json
import { TreeView, createTreeViewCollection } from '@skeletonlabs/skeleton-react';
import { FileIcon, FolderIcon } from 'lucide-react';
interface Node {
id: string;
name: string;
children?: Node[];
}
function TreeNode(props: { node: Node; indexPath: number[] }) {
return (
<TreeView.NodeProvider value={props}>
{props.node.children ? (
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchIndicator />
<TreeView.BranchText>
<FolderIcon className="size-4" />
{props.node.name}
</TreeView.BranchText>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{props.node.children.map((childNode, childIndex) => (
<TreeNode key={childNode.id} node={childNode} indexPath={[...props.indexPath, childIndex]} />
))}
</TreeView.BranchContent>
</TreeView.Branch>
) : (
<TreeView.Item>
<FileIcon className="size-4" />
{props.node.name}
</TreeView.Item>
)}
</TreeView.NodeProvider>
);
}
export default function Default() {
const collection = createTreeViewCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'root',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{
id: 'node_modules/@skeletonlabs',
name: '@skeletonlabs',
children: [
{
id: 'node_modules/@skeletonlabs/skeleton',
name: 'skeleton',
},
],
},
],
},
{
id: 'package.json',
name: 'package.json',
},
],
},
});
return (
<TreeView collection={collection}>
<TreeView.Label>File System</TreeView.Label>
<TreeView.Tree>
{collection.rootNode.children?.map((node, index) => (
<TreeNode node={node} indexPath={[index]} key={node.id} />
))}
</TreeView.Tree>
</TreeView>
);
}
Multiple Selection
- Windows: Hold Ctrl + left click with mouse.
- MacOS: Hold ⌘ + left click with mouse.
File System
node_modules
@skeletonlabs
skeleton
package.json
<script lang="ts">
import { FileIcon, FolderIcon } from '@lucide/svelte';
import { TreeView, createTreeViewCollection } from '@skeletonlabs/skeleton-svelte';
interface Node {
id: string;
name: string;
children?: Node[];
}
const collection = createTreeViewCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'root',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{
id: 'node_modules/@skeletonlabs',
name: '@skeletonlabs',
children: [
{
id: 'node_modules/@skeletonlabs/skeleton',
name: 'skeleton',
},
],
},
],
},
{
id: 'package.json',
name: 'package.json',
},
],
},
});
</script>
<TreeView {collection} selectionMode="multiple">
<TreeView.Label>File System</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children || [] as node, index (node)}
{@render treeNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView>
{#snippet treeNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider value={{ node, indexPath }}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchIndicator />
<TreeView.BranchText>
<FolderIcon class="size-4" />
{node.name}
</TreeView.BranchText>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as childNode, childIndex (childNode)}
{@render treeNode(childNode, [...indexPath, childIndex])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<FileIcon class="size-4" />
{node.name}
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
Collapse and Expand
Use the Provider Pattern to gain access to the collapse and expand methods on the TreeView instance.
File System
node_modules
@skeletonlabs
skeleton
package.json
<script lang="ts">
import { FileIcon, FolderIcon } from '@lucide/svelte';
import { TreeView, createTreeViewCollection, useTreeView } from '@skeletonlabs/skeleton-svelte';
interface Node {
id: string;
name: string;
children?: Node[];
}
const collection = createTreeViewCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'root',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{
id: 'node_modules/@skeletonlabs',
name: '@skeletonlabs',
children: [
{
id: 'node_modules/@skeletonlabs/skeleton',
name: 'skeleton',
},
],
},
],
},
{
id: 'package.json',
name: 'package.json',
},
],
},
});
const id = $props.id();
const treeView = useTreeView({
id: id,
collection: collection,
});
</script>
<div class="w-full flex flex-col items-center gap-4">
<TreeView.Provider value={treeView}>
<TreeView.Label>File System</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children || [] as node, index (node)}
{@render treeNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView.Provider>
<div class="flex gap-2">
<button class="btn preset-filled" onclick={() => treeView().collapse()}> Collapse </button>
<button class="btn preset-filled" onclick={() => treeView().expand()}> Expand </button>
</div>
</div>
{#snippet treeNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider value={{ node, indexPath }}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchIndicator />
<TreeView.BranchText>
<FolderIcon class="size-4" />
{node.name}
</TreeView.BranchText>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as childNode, childIndex (childNode)}
{@render treeNode(childNode, [...indexPath, childIndex])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<FileIcon class="size-4" />
{node.name}
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
Lazy Loading
File System
node_modules
package.json
<script lang="ts">
import { FileIcon, FolderIcon, LoaderIcon } from '@lucide/svelte';
import { TreeView, createTreeViewCollection, type TreeViewRootProps } from '@skeletonlabs/skeleton-svelte';
interface Node {
id: string;
name: string;
children?: Node[];
childrenCount?: number;
}
const response: Record<string, Node[]> = {
node_modules: [
{
id: 'node_modules/@skeletonlabs',
name: '@skeletonlabs',
childrenCount: 3,
},
],
'node_modules/@skeletonlabs': [
{
id: 'node_modules/@skeletonlabs/skeleton',
name: 'skeleton',
},
{
id: 'node_modules/@skeletonlabs/skeleton-react',
name: 'skeleton-react',
},
{
id: 'node_modules/@skeletonlabs/skeleton-svelte',
name: 'skeleton-svelte',
},
],
};
let collection = $state(
createTreeViewCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'root',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
childrenCount: 1,
},
{
id: 'package.json',
name: 'package.json',
},
],
},
}),
);
const loadChildren: TreeViewRootProps['loadChildren'] = async (details) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return response[details.node.id] || [];
};
const onLoadChildrenComplete: TreeViewRootProps['onLoadChildrenComplete'] = (details) => {
collection = details.collection;
};
</script>
<TreeView {collection} {loadChildren} {onLoadChildrenComplete}>
<TreeView.Label>File System</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children || [] as node, index (node)}
{@render treeNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView>
{#snippet treeNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider value={{ node, indexPath }}>
{#if node.children || node.childrenCount}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchIndicator class="data-loading:hidden" />
<TreeView.BranchIndicator class="hidden data-loading:inline animate-spin">
<LoaderIcon class="size-4" />
</TreeView.BranchIndicator>
<TreeView.BranchText>
<FolderIcon class="size-4" />
{node.name}
</TreeView.BranchText>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children ?? [] as childNode, childIndex (childNode)}
{@render treeNode(childNode, [...indexPath, childIndex])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<FileIcon class="size-4" />
{node.name}
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
API Reference
Root
| Property | Default | Type |
|---|---|---|
collection | - | TreeCollection<any> | undefinedThe tree collection data |
ids | - | Partial<{ root: string; tree: string; label: string; node: (value: string) => string; }> | undefined The ids of the tree elements. Useful for composition. |
expandedValue | - | string[] | undefinedThe controlled expanded node ids |
defaultExpandedValue | - | string[] | undefinedThe initial expanded node ids when rendered. Use when you don't need to control the expanded node value. |
selectedValue | - | string[] | undefinedThe controlled selected node value |
defaultSelectedValue | - | string[] | undefinedThe initial selected node value when rendered. Use when you don't need to control the selected node value. |
defaultCheckedValue | - | string[] | undefinedThe initial checked node value when rendered. Use when you don't need to control the checked node value. |
checkedValue | - | string[] | undefinedThe controlled checked node value |
defaultFocusedValue | - | string | null | undefinedThe initial focused node value when rendered. Use when you don't need to control the focused node value. |
focusedValue | - | string | null | undefinedThe value of the focused node |
selectionMode | "single" | "single" | "multiple" | undefinedWhether the tree supports multiple selection - "single": only one node can be selected - "multiple": multiple nodes can be selected |
onExpandedChange | - | ((details: ExpandedChangeDetails<any>) => void) | undefinedCalled when the tree is opened or closed |
onSelectionChange | - | ((details: SelectionChangeDetails<any>) => void) | undefinedCalled when the selection changes |
onFocusChange | - | ((details: FocusChangeDetails<any>) => void) | undefinedCalled when the focused node changes |
onCheckedChange | - | ((details: CheckedChangeDetails) => void) | undefinedCalled when the checked value changes |
canRename | - | ((node: any, indexPath: IndexPath) => boolean) | undefinedFunction to determine if a node can be renamed |
onRenameStart | - | ((details: RenameStartDetails<any>) => void) | undefinedCalled when a node starts being renamed |
onBeforeRename | - | ((details: RenameCompleteDetails) => boolean) | undefinedCalled before a rename is completed. Return false to prevent the rename. |
onRenameComplete | - | ((details: RenameCompleteDetails) => void) | undefinedCalled when a node label rename is completed |
onLoadChildrenComplete | - | ((details: LoadChildrenCompleteDetails<any>) => void) | undefinedCalled when a node finishes loading children |
onLoadChildrenError | - | ((details: LoadChildrenErrorDetails<any>) => void) | undefinedCalled when loading children fails for one or more nodes |
expandOnClick | true | boolean | undefinedWhether clicking on a branch should open it or not |
typeahead | true | boolean | undefinedWhether the tree supports typeahead search |
loadChildren | - | ((details: LoadChildrenDetails<any>) => Promise<any[]>) | undefinedFunction to load children for a node asynchronously. When provided, branches will wait for this promise to resolve before expanding. |
dir | "ltr" | "ltr" | "rtl" | undefinedThe document's text/writing direction. |
getRootNode | - | (() => ShadowRoot | Node | Document) | undefinedA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. |
element | - | ((attributes: HTMLAttributes<"div">) => Element) | undefinedRender the element yourself |
RootProvider
| Property | Default | Type |
|---|---|---|
value | - | TreeViewApi<PropTypes, any> |
element | - | ((attributes: HTMLAttributes<"div">) => Element) | undefinedRender the element yourself |
RootContext
| Property | Default | Type |
|---|---|---|
children | - | (treeView: TreeViewApi<PropTypes, any>) => ReactNode |
Tree
| Property | Default | Type |
|---|---|---|
element | - | ((attributes: HTMLAttributes<"div">) => Element) | undefinedRender the element yourself |
Label
| Property | Default | Type |
|---|---|---|
level | - | 1 | 2 | 3 | 4 | 5 | 6 | undefinedThe level of the heading. |
element | - | ((attributes: HTMLAttributes<"h3">) => Element) | undefinedRender the element yourself |
NodeProvider
| Property | Default | Type |
|---|---|---|
value | - | NodeProps |
children | - | ReactNode |
Branch
| Property | Default | Type |
|---|---|---|
element | - | ((attributes: HTMLAttributes<"div">) => Element) | undefinedRender the element yourself |
BranchControl
| Property | Default | Type |
|---|---|---|
element | - | ((attributes: HTMLAttributes<"div">) => Element) | undefinedRender the element yourself |
BranchText
| Property | Default | Type |
|---|---|---|
element | - | ((attributes: HTMLAttributes<"span">) => Element) | undefinedRender the element yourself |
BranchIndicator
| Property | Default | Type |
|---|---|---|
element | - | ((attributes: HTMLAttributes<"span">) => Element) | undefinedRender the element yourself |
BranchContent
| Property | Default | Type |
|---|---|---|
element | - | ((attributes: HTMLAttributes<"div">) => Element) | undefinedRender the element yourself |
BranchIndentGuide
| Property | Default | Type |
|---|---|---|
element | - | ((attributes: HTMLAttributes<"div">) => Element) | undefinedRender the element yourself |
Item
| Property | Default | Type |
|---|---|---|
element | - | ((attributes: HTMLAttributes<"div">) => Element) | undefinedRender the element yourself |