Tree
A project panel: collapsible folders, an icon per file type and git status colors. TreeNode.from_paths(paths, status=...) builds the nodes, puts folders first and propagates the git status to the folders that contain changes. Each node’s id is its path; you own the expanded set and the selected id.
ui.tree · Category: Lists and menus · Full reference →


nodes = ui.TreeNode.from_paths( ["src/main.rs", "src/app.rs", "src/ui/button.rs", "src/ui/mod.rs", "tests/app.rs", "Cargo.toml"], status={"src/app.rs": "modified", "src/ui/button.rs": "created"},)pz.container(ui.tree(nodes, expanded={"src", "src/ui"}, selected="src/app.rs", on_toggle=on_toggle, on_select=on_select), width=280)When to use
Section titled “When to use”- For files and any hierarchy: outlines, categories, settings sections.
statusis"modified","created","deleted","conflict"or"ignored".
import pyzed as pzfrom pyzed import ui
class Explorer(pz.App): def __init__(self): self.nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "Cargo.toml"]) self.expanded = {"src"} self.selected = None
def toggle(self, folder): self.expanded ^= {folder}
def view(self): return ui.tree(self.nodes, expanded=self.expanded, selected=self.selected, on_toggle=self.toggle, on_select=lambda path: setattr(self, "selected", path))Variants
Section titled “Variants”Expanded
Section titled “Expanded”

Code
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])pz.container(ui.tree(nodes, expanded=set()), width=240)

expanded={"src"}Code
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])pz.container(ui.tree(nodes, expanded={"src"}), width=240)

expanded={"src", "src/ui"}Code
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])pz.container(ui.tree(nodes, expanded={"src", "src/ui"}, selected="src/ui/button.rs"), width=240)Git status
Section titled “Git status”

status=Code
nodes = ui.TreeNode.from_paths(["src/app.rs", "src/new.rs", "src/old.rs", "build.log"], status={"src/app.rs": "modified", "src/new.rs": "created", "src/old.rs": "deleted", "build.log": "ignored"})pz.container(ui.tree(nodes, expanded={"src"}), width=240)

"conflict"Code
nodes = ui.TreeNode.from_paths(["merge.py", "ok.py"], status={"merge.py": "conflict"})pz.container(ui.tree(nodes, expanded=set()), width=240)Density
Section titled “Density”

item_height=20, indent=10Code
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])pz.container(ui.tree(nodes, expanded={"src", "src/ui"}, item_height=20, indent=10), width=240)

item_height=30, indent=20Code
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])pz.container(ui.tree(nodes, expanded={"src"}, item_height=30, indent=20), width=240)States and events
Section titled “States and events”on_open(id) runs on a double click of a file and on_context(id) on a right click.


Code
pz.container(ui.tree(ui.TreeNode.from_paths(["src/app.py", "src/ui.py", "README.md"]), expanded={"src"}, selected={"src/app.py", "README.md"}, focused="src/ui.py", on_open=on_open, on_context=on_context), width=300)Examples
Section titled “Examples”A real folder
Section titled “A real folder”Build the paths with pathlib and pass them to from_paths.
from pathlib import Path
root = Path(".")paths = [p.relative_to(root).as_posix() for p in root.rglob("*") if p.is_file() and ".git" not in p.parts]nodes = ui.TreeNode.from_paths(paths)Nodes by hand
Section titled “Nodes by hand”Folders are nodes with children.


nodes = [ ui.TreeNode("Inbox", children=[ui.TreeNode("Welcome", id="inbox/1", icon="mail")], id="inbox"), ui.TreeNode("Archive", children=[], id="archive"), ui.TreeNode("Notes.md", id="notes", icon="notebook-pen"),]pz.container(ui.tree(nodes, expanded={"inbox"}, selected="inbox/1"), width=260)Events
Section titled “Events”Tree has 4 events of its own, listed below. Like every widget it also has the 18 common events (click, double_click, right_click, mouse_enter, drag, key_down, resize…), attached with .on(...): see Events and Events of every component.
| Event | Handler gets | When |
|---|---|---|
on_toggle |
(id) |
Called with the id of the clicked folder. |
on_select |
(id) |
Called with the id of the clicked file. |
on_open |
(id) |
Called with the id of a file that is double-clicked. |
on_context |
(id) |
Called with the id of a row that is right-clicked (show a ui.menu there). |
Handler gets is what Pyzed passes to your callable: () means nothing. Any handler can also be a message (a value that is not callable), which goes to update().
Handling the events
Section titled “Handling the events”The same events written three ways. Imperative: methods of the app change self. Declarative: the handlers produce messages and one update() decides. Functional: a pure function draws an immutable state and asks for changes with set_state(...). See Imperative, declarative and functional.
class Explorer(pz.App): def __init__(self): self.nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "Cargo.toml"]) self.expanded = {"src"} self.selected = None
def toggle(self, folder): # on_toggle: the folder's id (its path) self.expanded ^= {folder}
def view(self): return ui.tree(self.nodes, expanded=self.expanded, selected=self.selected, on_toggle=self.toggle, on_select=lambda path: setattr(self, "selected", path), # click on a file on_open=lambda path: print("open", path), # double click on_context=lambda path: print("menu for", path)) # right clickfrom dataclasses import dataclass
@dataclassclass Toggle: folder: str
@dataclassclass Select: path: str
class Explorer(pz.App): nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "Cargo.toml"]) expanded = frozenset({"src"}) selected = None
def update(self, message): match message: case Toggle(folder): self.expanded = self.expanded ^ {folder} case Select(path): self.selected = path
def view(self): return ui.tree(self.nodes, expanded=self.expanded, selected=self.selected, on_toggle=Toggle, on_select=Select)from dataclasses import dataclass
NODES = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "Cargo.toml"])
@dataclass(frozen=True)class State: expanded: frozenset = frozenset({"src"}) selected: str = ""
def view(state, set_state): return ui.tree(NODES, expanded=state.expanded, selected=state.selected, on_toggle=lambda folder: set_state(expanded=state.expanded ^ {folder}), on_select=lambda path: set_state(selected=path))Properties and API
Section titled “Properties and API”ui.tree
Section titled “ui.tree”tree( nodes, *, expanded, selected=None, on_toggle=None, on_select=None, on_open=None, on_context=None, focused=None, indent=14, item_height=24, theme=None,)A collapsible file tree, like a code editor’s project panel.
| Property | Default | Description |
|---|---|---|
nodes |
required | List of TreeNode. |
expanded |
required | Set of open folder ids. |
selected |
None |
One id or a collection of ids (multiple selection). |
focused |
None |
Outlines the row the keyboard is on. |
indent |
14 |
Left indentation, in pixels. |
item_height |
24 |
Height of each row. |
theme |
None |
Theme to use instead of the current one (pyzed.theme.current()). |
Events: on_toggle, on_select, on_open, on_context (events).
ui.TreeNode
Section titled “ui.TreeNode”@dataclassclass TreeNode(name, children=None, id=None, status=None, icon=None, icon_color=None)A file or folder for ui.tree. Folders have children (a list). status is "modified", "created", "deleted", "conflict" or "ignored". TreeNode.from_paths(paths, status=None) builds the tree from paths like "src/main.rs", with folders first and the git status propagated to folders.
| Property | Default | Description |
|---|---|---|
name |
required | Display name. |
children |
None |
Children (None for a file). |
id |
None |
Stable identifier used to focus or scroll it with a Command. |
status |
None |
Git status. |
icon |
None |
Name of a Lucide icon (pyzed icons lists them). |
icon_color |
None |
Icon color: a theme token or a hex color. |