Skip to content
Introducing AI Conversations: Natural Language Interaction for Your Apps! Learn More

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:

<TreeView TItem="FileNode"
SelectionMode="BlazorTreeSelectionMode.Multiple"
SelectedItems="selected"
SelectedItemsChanged="v => selected = v"
ItemsSource="rootItems" />
@code {
IList<FileNode> selected = new List<FileNode>();
}
<TreeView TItem="FileNode"
EnableDragDrop="true"
ItemDropped="OnDropped"
ItemsSource="rootItems"
ChildrenSelector="@(n => n.Children)" />
@code {
void OnDropped(TreeItemDroppedEventArgs<FileNode> e)
{
var srcList = FindParentList(e.SourceItem);
var tgtList = FindParentList(e.TargetItem);
srcList.Remove(e.SourceItem);
tgtList.Insert(tgtList.IndexOf(e.TargetItem) + 1, e.SourceItem);
StateHasChanged();
}
}

Drops onto descendants are rejected automatically.

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

KeyAction
/ 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 / SpaceSelect the focused row
Home / EndJump to first / last visible row
ParameterTypeDefaultDescription
ItemsSourceIEnumerable<TItem>Source for root items
RootLoaderFunc<Task<IEnumerable<TItem>>>Async loader for roots (overrides ItemsSource)
ChildrenSelectorFunc<TItem, IEnumerable<TItem>?>Sync children getter
ChildrenLoaderFunc<TItem, Task<IEnumerable<TItem>>>Async children loader (fallback when selector returns null)
HasChildrenSelectorFunc<TItem, bool>Whether the row should render a chevron
CanExpandSelectorFunc<TItem, bool>Gate expansion gesture
CanSelectSelectorFunc<TItem, bool>Gate selection gesture
SelectionModeBlazorTreeSelectionModeSingleNone / Single / Multiple
IndentSizedouble20Pixels of indent per depth level
ChevronSizedouble14Pixels
ChevronColorstring"#666"CSS color used for glyph fallbacks
ShowGuideLinesboolfalseVertical connector lines
EnableDragDropboolfalseEnables HTML5 drag/drop with ItemDropped event
CssClassstring?Extra class added to the root <div>
await tree.ExpandAsync(item);
await tree.CollapseAsync(item);
await tree.ExpandAllAsync();
tree.CollapseAll();
await tree.RefreshAsync(item); // drops cache for this node
await tree.ReloadAsync(); // re-runs RootLoader or rebinds ItemsSource