Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

TreeView | Blazor Usage

The Blazor <TreeView TItem> component mirrors the MAUI control. It’s strongly typed in the item type, icons are RenderFragment slots, and standard arrow-key navigation is built in.

Install the NuGet package:

Terminal window
dotnet add package Shiny.Blazor.Controls

Add the @using directive — typically in _Imports.razor:

@using Shiny.Blazor.Controls
<TreeView TItem="FileNode"
ItemsSource="rootItems"
ChildrenSelector="@(n => n.Children)"
HasChildrenSelector="@(n => n.IsFolder)"
CanSelectSelector="@(n => !n.IsLocked)"
SelectedItem="selected"
SelectedItemChanged="v => selected = v"
ItemExpanded="OnExpanded">
<ItemTemplate Context="node">
<span>@node.Icon</span>
<span>@node.Name</span>
</ItemTemplate>
</TreeView>
@code {
FileNode? selected;
List<FileNode> rootItems = new() { /* ... */ };
void OnExpanded(TreeItemEventArgs<FileNode> e) => Console.WriteLine($"Expanded {e.Item.Name}");
public class FileNode
{
public string Name { get; set; } = "";
public string Icon { get; set; } = "";
public bool IsFolder { get; set; }
public bool IsLocked { get; set; }
public List<FileNode>? Children { get; set; }
}
}
<TreeView TItem="FileNode"
@ref="tree"
ItemsSource="rootItems"
ChildrenLoader="LoadChildren"
HasChildrenSelector="@(n => n.IsFolder)"
LoadFailed="e => status = $\"Failed: {e.Exception.Message}\"">
<ItemTemplate Context="node">@node.Name</ItemTemplate>
</TreeView>
@code {
TreeView<FileNode>? tree;
string status = "";
async Task<IEnumerable<FileNode>> LoadChildren(FileNode parent)
{
await Task.Delay(500);
return await myService.GetChildrenAsync(parent.Id);
}
// Programmatic API:
async Task RefreshNode(FileNode n) => await tree!.RefreshAsync(n);
async Task ExpandAll() => await tree!.ExpandAllAsync();
}

You can mix sync and lazy branches in the same tree — ChildrenSelector is consulted first, then ChildrenLoader is the fallback when the selector returns null.

<TreeView TItem="FileNode" ItemsSource="rootItems" ChildrenSelector="@(n => n.Children)">
<ExpandedIcon>
<i class="fa-solid fa-chevron-down" style="color:#7C3AED"></i>
</ExpandedIcon>
<CollapsedIcon>
<i class="fa-solid fa-chevron-right" style="color:#7C3AED"></i>
</CollapsedIcon>
<RetryIcon>
<i class="fa-solid fa-rotate-right" style="color:#DC2626"></i>
</RetryIcon>
<LoadingTemplate>
<span class="spinner-border spinner-border-sm" />
</LoadingTemplate>
<ItemTemplate Context="node">
@node.Name
</ItemTemplate>
</TreeView>

If no slot is supplied, the control falls back to the built-in glyphs (, , ) coloured via the ChevronColor parameter.

<TreeView TItem="FileNode"
SelectionMode="BlazorTreeSelectionMode.Single"
SelectedItem="selected"
SelectedItemChanged="v => selected = v"
ItemsSource="rootItems" />

For multi-select — every row gets a checkbox:

<TreeView TItem="FileNode"
SelectionMode="BlazorTreeSelectionMode.Multiple"
SelectedItems="selected"
SelectedItemsChanged="v => selected = v"
ItemsSource="rootItems" />
@code {
IList<FileNode> selected = new List<FileNode>();
}

The checkbox and the row are both hit targets — either toggles the row. Rows blocked by CanSelectSelector render a disabled box. Set ShowSelectionCheckBoxes="false" for the older highlight-only look. Changing SelectionMode clears the current selection.

Select or clear in bulk:

await tree.SelectAllAsync(); // everything selectable and loaded
await tree.SetBranchSelectedAsync(folder, true);// one item and its loaded descendants
await tree.DeselectAllAsync(); // clears in any mode

Only nodes the tree has materialized can be checked — await tree.ExpandAllAsync() first to select a lazy tree in full.

Drag/drop runs on native HTML5 drag events registered by a small JS module that loads automatically when EnableDragDrop="true" — not Blazor’s synthetic events, which can’t call dataTransfer.setData() and therefore break Safari and Firefox.

ItemDropped receives TreeItemDroppedEventArgs<TItem> with SourceItem, TargetItem, and Position (BlazorTreeDropPosition.Above / Below to reorder among siblings, Into to move into a folder). Rows show drop indicators while dragging: a horizontal line for above/below, a highlight for into.

<TreeView TItem="FileNode"
@ref="tree"
EnableDragDrop="true"
ItemDropped="OnDropped"
ItemsSource="rootItems"
ChildrenSelector="@(n => n.Children)" />
@code {
TreeView<FileNode>? tree;
async Task OnDropped(TreeItemDroppedEventArgs<FileNode> e)
{
var srcList = FindParentList(e.SourceItem);
if (e.Position == BlazorTreeDropPosition.Into)
{
srcList.Remove(e.SourceItem);
e.TargetItem.Children ??= new();
e.TargetItem.Children.Add(e.SourceItem);
}
else
{
var tgtList = FindParentList(e.TargetItem);
srcList.Remove(e.SourceItem);
var idx = tgtList.IndexOf(e.TargetItem);
tgtList.Insert(e.Position == BlazorTreeDropPosition.Above ? idx : idx + 1, e.SourceItem);
}
await tree!.ReloadAsync(); // rebuilds while preserving expansion/selection
if (e.Position == BlazorTreeDropPosition.Into)
await tree!.ExpandAsync(e.TargetItem);
}
}

Drops onto descendants are rejected automatically.

The Blazor TreeView is focusable (tabindex="0") and supports:

Key Action
/ Move focus to previous/next visible row
Expand collapsed node, or move into first child if already expanded
Collapse expanded node, or move focus to parent
Enter / Space Select the focused row
Home / End Jump to first / last visible row
Parameter Type Default Description
ItemsSource IEnumerable<TItem> Source for root items
RootLoader Func<Task<IEnumerable<TItem>>> Async loader for roots (overrides ItemsSource)
ChildrenSelector Func<TItem, IEnumerable<TItem>?> Sync children getter
ChildrenLoader Func<TItem, Task<IEnumerable<TItem>>> Async children loader (fallback when selector returns null)
HasChildrenSelector Func<TItem, bool> Whether the row should render a chevron
CanExpandSelector Func<TItem, bool> Gate expansion gesture
CanSelectSelector Func<TItem, bool> Gate selection gesture
SelectionMode BlazorTreeSelectionMode Single None / Single / Multiple. Changing it clears the selection
ShowSelectionCheckBoxes bool true Checkbox on every row while in Multiple mode
IndentSize double 20 Pixels of indent per depth level
ChevronSize double 14 Pixels
ChevronColor string "#666" CSS color used for glyph fallbacks
ShowGuideLines bool false Vertical connector lines
EnableDragDrop bool false Enables native HTML5 drag/drop (via JS interop) with the ItemDropped event
ItemDropped EventCallback<TreeItemDroppedEventArgs<TItem>> Fires after a drop with SourceItem / TargetItem / Position
CssClass string? Extra class added to the root <div>
await tree.ExpandAsync(item);
await tree.CollapseAsync(item);
await tree.ExpandAllAsync(); // optional maxDepth (default 32) guards cyclic/endless trees
tree.CollapseAll();
await tree.SelectAllAsync(); // Multiple mode only
await tree.DeselectAllAsync();
await tree.SetBranchSelectedAsync(item, true);
await tree.RefreshAsync(item); // drops cache for this node
await tree.ReloadAsync(); // rebuilds from data, preserving expansion/selection for items that still exist
var node = tree.FindNode(item); // locate the wrapper node for any source item