Tree
Browse hierarchical content with expansion and selection.

use blinc_cn as cn;
use blinc_core::reactive::{Signal, computed};
use blinc_layout::{binding::IntoReactive, prelude::*};
pub fn tree_demo(choice: Signal<String>) -> Div {
div()
.class("demo-stack")
.child(
cn::tree_view()
.with_guides()
.node("src", "src", |node| {
node.expanded()
.child("main", "main.rs", |node| node)
.child("ui", "ui.rs", |node| node)
})
.node("assets", "assets", |node| node.child("theme", "theme.css", |node| node))
.on_select(move |key| choice.set(key.to_string())),
)
.child(
bound_text(computed(move |g| {
g.get(choice)
.filter(|v| v != "rust")
.map(|v| format!("Selected: {v}"))
.unwrap_or_else(|| "Select a file".into())
}))
.class("demo-caption"),
)
}
fn bound_text(value: impl IntoReactive<String>) -> Text {
Text::bound(value.into_reactive())
}
@layer components {
.cn-tree {
font-family: var(--font-sans);
}
.cn-tree {
font-size: var(--text-sm);
}
.cn-tree-row {
border-radius: var(--radius-md);
color: var(--text-secondary);
background: transparent;
cursor: pointer;
outline: 0 solid var(--focus-ring);
outline-offset: -2px;
transition: background var(--duration-fast) var(--ease-state),
color var(--duration-fast) var(--ease-state);
}
.cn-tree-row:hover {
background: var(--accent-subtle);
color: var(--text-primary);
}
.cn-tree-row[data-selected],
.cn-tree-row[data-selected]:hover {
background: color-mix(in srgb, var(--primary) 24%, transparent) !important;
color: var(--text-primary) !important;
}
.cn-tree-row[data-focus-visible] {
outline-width: 2px;
}
.cn-tree-row[data-diff="added"] {
color: var(--success);
}
.cn-tree-row[data-diff="removed"] {
color: var(--error);
}
.cn-tree-row[data-diff="modified"] {
color: var(--warning);
}
.cn-tree-chevron {
color: var(--text-tertiary);
transition: transform var(--duration-normal) var(--ease-state);
}
.cn-tree-item[data-state="open"] > .cn-tree-row > .cn-tree-chevron {
transform: rotate(90deg);
}
.cn-tree-icon {
color: var(--text-tertiary);
}
.cn-tree-row[data-selected] > .cn-tree-icon {
color: var(--primary) !important;
}
.cn-tree-group {
overflow: hidden;
}
.cn-tree-group[data-state="closed"] {
height: 0;
}
.cn-tree-guide {
background: transparent;
}
.cn-tree[data-guides] .cn-tree-guide {
background: var(--border);
}
}
Usage and properties
Browse hierarchical content with expansion and selection. Apply CSS after the component layer to restyle the documented parts.
Use the installation guide to add blinc_cn and its component stylesheet. Create the fixture bindings once, and return the demo from your UI builder. Values marked Bound accept Signal/Computed sources where the API supports them; structural options configure the component at construction.
| Property | Type | Update model | This example |
|---|---|---|---|
node / child | tree builders | Construction | src, main.rs, ui.rs and assets |
on_select | Fn(&str) | Callback | Updates values.choice |
Keyboard and interaction
Click a row, then Up and Down walk the shown rows, Right opens a row or enters its children, Left closes it or goes to its parent, Home and End jump to the ends, and Enter or Space selects.
Customizing it
Add application CSS after the default component layer. These selectors are the restyling surface for this component:
.cn-tree.cn-tree-row.cn-tree-chevron.cn-tree-chevron-space.cn-tree-icon.cn-tree-label.cn-tree-item.cn-tree-group.cn-tree-guide
Theme and component variables: --primary, --text-primary, --text-secondary, --surface, --border, --radius-default, --duration-fast, --ease-state.
Built-in themes
The Live theme control installs Blinc's own theme bundles, including their colours, focus rings, corner shapes and motion tokens.
pub fn theme_bundle_for(name: &str) -> blinc_theme::ThemeBundle {
match name {
"restrained" => blinc_theme::RestrainedTheme::bundle(),
"expressive" => blinc_theme::ExpressiveTheme::bundle(),
_ => blinc_theme::HybridTheme::bundle(),
}
.with_css(blinc_cn::cn_styles::CN_STYLES)
}Download the default widget and component stylesheets. Sample assets and licenses are available in the asset guide.
Documentation
From the canonical component guide.
tree_view() adds roots with .node(key, label, configure) and nested nodes
with .child(key, label, configure):
let chosen = ctx.use_signal("main".to_string());
div().flex_col().gap(2.0)
.child(tree_view()
.node("src", "src", |n| {
n.expanded()
.child("main", "main.rs", |n| n.icon(to_svg(icons::FILE, 16.0)))
.child("ui", "ui", |n| {
n.child("button", "button.rs", |n| n)
})
})
.node("cargo", "Cargo.toml", |n| n)
.selected("main")
.on_select(move |key| chosen.set(key.to_string())))
.child(Text::bound(chosen.into_reactive()))
.selected(..) sets an initial key. .on_select(..) reports subsequent
selection; it does not bind the tree's selection to an external signal.
Clicking a row selects it and opens or closes it. Once the tree has been clicked, Up and Down walk the shown rows, Right opens a row or enters its children, Left closes it or goes to its parent, Home and End jump to the ends, and Enter or Space selects.
A node's .diff(TreeNodeDiff::Added) (or Removed, Modified) colours its
row. .guides(false) hides the guide lines; .indent(px) sets the indent
(14px by default).
Styling
Each .cn-tree-item holds a .cn-tree-row and, with children, a
.cn-tree-group. A row carries data-selected, data-diff and, while the
keys are on it, data-focus-visible. An item and its group carry
data-state="open" or "closed". The other parts are .cn-tree-chevron,
.cn-tree-icon, .cn-tree-label and .cn-tree-guide:
.cn-tree-row[data-selected] { background: var(--accent-subtle); }
.cn-tree-row[data-diff="added"] { color: #22c55e; }
.cn-tree[data-guides] .cn-tree-guide { background: var(--text-tertiary); }
Platforms and version
The component API is available in Blinc 0.6.0. The Live example runs in a dedicated worker and requires a compatible GPU surface. Preview and Code remain available when Live cannot start. The canvas has a descriptive label; a full screen-reader DOM mirror is not provided. The gallery integration is based on revision e2e55c24e078.
Recordings and live demos use the default Hybrid theme with bundled Noto Sans. The recording uses a 560 × 320 logical viewport at twice that resolution. Live follows its container and actual display density. Source base revision · Exact build source · Artifact versions and checksums · Font license.