Skip to content

Tree

<caos-tree>

a hierarchy of nodes, grouped or nested, that a person can move through and choose from

May be placed on a record page, an app’s home page, a page inside an app and a seam in the workspace frame.

Name Attribute Description Type Default Required
nodes property only The tree itself — a list of nodes, each with an id and a label. A node carrying children is a branch, rendering as a heading over its own nested list (to any depth); a node with none is a leaf, rendering as a row a person can choose. A node may also carry an icon, an href and a badge. Cannot ride an attribute, so it is set as a property. json none declared Required
selectedId selected-id, markup only The id of the currently-active row. Marks it .is-active and aria-current="page", and gives it the component’s single tab stop, so tabbing in lands where the reader already is. text none declared Optional
collapsible collapsible, markup only Lets a reader open and close branches. Every branch still starts OPEN: a hierarchy is placed to be seen, so nothing is hidden on arrival. Without this the tree holds every branch open and says nothing about expansion, which is what a permanently open branch should say rather than claiming to be openable. boolean false Optional
expandedIds property only Which branches are open, as ids — readable to save a reader’s place, settable to restore it. Setting it closes every branch not named. A branch that arrives in new data is open, because it is not something the reader chose to close. Only meaningful alongside collapsible. Cannot ride an attribute, so it is set as a property. json none declared Optional
filter filter, markup only Narrows the tree to rows whose label contains this text (case-insensitive). A branch survives only while one of its rows still matches, and every branch is held open while a filter runs, so a match is never left behind a closed one. Closed branches are remembered, not discarded: clearing the filter returns the tree to the shape the reader left it in. text none declared Optional
heading property only The rail’s heading — { label, icon?, href?, id? }. The console’s name, its mark, and the way back to its landing; it reads as current while that landing is what is on screen. Omitted ⇒ a bare list with no heading. Cannot ride an attribute, so it is set as a property. json none declared Optional
filterable filterable, markup only Draw a find box above the list, which narrows the list as somebody types. Without it the list is narrowed only by whatever the host puts in filter. boolean false Optional
findLabel find-label, markup only What the find box is called for a screen reader. text Quick Find Optional
findPlaceholder find-placeholder, markup only The prompt inside the find box before anything is typed. text Quick Find… Optional
emptyFilterMessage property only A function of the filter text, wording “nothing matches” for what the host is filtering. This piece knows only that a filter came up empty, not what it is a filter over — so the host names that, not the tree. Cannot ride an attribute, so it is set as a property. json No matches for “”. Optional
Name When it fires What it carries
tree-choose A leaf is chosen — by click, or by Enter on it. { id, href? } — the id of the row that was chosen, and its href when it declared one.
tree-toggle A reader opens or closes a branch, by clicking its heading or by keyboard. { id, expanded } — the branch, and whether it is now open. Only a collapsible tree raises it.

Nothing goes inside this piece.

Design token What it controls
--caos-state-selected-bg The fill behind the current row. The platform’s selected value (CAOS-586), not this piece’s own.
--caos-state-hover-bg The fill behind a row under the pointer.

Nothing else drives this piece by calling it.

Nothing on this piece can be read back.

This piece exposes none of its insides to a stylesheet.

Any list of things a person picks from or reads that is grouped or nested — a navigation rail, a section menu, a picker over categories, a hierarchy of records under their parents. Reach for it instead of hand-building rows, so the grouping, the indentation, the active-row highlight, the icons, the badges, the filter and the keyboard all behave the same everywhere they appear.

  • SegmentedFilter — A flat row of a few mutually-exclusive choices, not a grouped or nested list.
  • Breadcrumb — Showing where a person already is, rather than offering a list to choose from.
  • DataTable — The rows carry columns of their own. A table with expandable rows keeps the columns and still nests; a tree has one label per node.

The whole tree is one tab stop, not one per row. Inside it the arrow keys move focus: down and up through the rows on screen, right to open a closed branch and then into it, left to close an open branch and then out to its parent, Home and End to the first and last, and typing letters jumps to the next label starting with them. Enter opens a leaf; Enter or Space opens or closes a branch. Every node is a treeitem inside a tree, each carrying its level, how many siblings it has and which one it is, and each branch holds its children in its own group — so a branch is announced as standing over its rows rather than merely beside them. A collapsible branch says whether it is open; one that cannot be closed says nothing, rather than claiming an ability it does not have. The active row is marked aria-current="page".

The rail as a console places it: a heading leading back to the landing, a find box that narrows the list as somebody types, and the sections beneath it.

{
"id": "example",
"section": "A whole console side navigation",
"columns": 1,
"items": [
{
"id": "tree_1",
"type": "component",
"key": "tree",
"inputs": {
"heading": {
"label": "Setup",
"icon": "settings",
"href": "/setup"
},
"filterable": true,
"find_placeholder": "Quick Find…",
"selected_id": "roles",
"nodes": [
{
"id": "admin",
"label": "Administration",
"children": [
{
"id": "permission-sets",
"label": "Permission Sets",
"icon": "shield-check",
"href": "/setup/permission-sets"
},
{
"id": "roles",
"label": "Roles",
"icon": "shield-check",
"href": "/setup/roles"
}
]
},
{
"id": "objects",
"label": "Objects & Fields",
"children": [
{
"id": "object-manager",
"label": "Object Manager",
"icon": "boxes",
"href": "/setup/object-manager"
}
]
}
]
}
}
]
}
<caos-tree id="tree1" filterable find-placeholder="Quick Find…" selected-id="roles"></caos-tree>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const tree1 = document.getElementById('tree1');
tree1.heading = {
"label": "Setup",
"icon": "settings",
"href": "/setup"
};
tree1.nodes = [
{
"id": "admin",
"label": "Administration",
"children": [
{
"id": "permission-sets",
"label": "Permission Sets",
"icon": "shield-check",
"href": "/setup/permission-sets"
},
{
"id": "roles",
"label": "Roles",
"icon": "shield-check",
"href": "/setup/roles"
}
]
},
{
"id": "objects",
"label": "Objects & Fields",
"children": [
{
"id": "object-manager",
"label": "Object Manager",
"icon": "boxes",
"href": "/setup/object-manager"
}
]
}
];

Two groups — Administration and Objects & Fields — with an icon on most rows, a “Soon” badge on one that is not live yet, and Object Manager pre-selected as the active row. Not collapsible: a rail keeps every destination in sight.

{
"id": "example",
"section": "A Setup-style rail",
"columns": 1,
"items": [
{
"id": "tree_1",
"type": "component",
"key": "tree",
"inputs": {
"selected_id": "object-manager",
"nodes": [
{
"id": "admin",
"label": "Administration",
"children": [
{
"id": "permission-sets",
"label": "Permission Sets",
"icon": "shield-check"
},
{
"id": "roles",
"label": "Roles",
"icon": "shield-check"
}
]
},
{
"id": "objects",
"label": "Objects & Fields",
"children": [
{
"id": "object-manager",
"label": "Object Manager",
"icon": "boxes"
},
{
"id": "value-sets",
"label": "Value Sets",
"icon": "list",
"badge": "Soon"
}
]
}
]
}
}
]
}
<caos-tree id="tree1" selected-id="object-manager"></caos-tree>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const tree1 = document.getElementById('tree1');
tree1.nodes = [
{
"id": "admin",
"label": "Administration",
"children": [
{
"id": "permission-sets",
"label": "Permission Sets",
"icon": "shield-check"
},
{
"id": "roles",
"label": "Roles",
"icon": "shield-check"
}
]
},
{
"id": "objects",
"label": "Objects & Fields",
"children": [
{
"id": "object-manager",
"label": "Object Manager",
"icon": "boxes"
},
{
"id": "value-sets",
"label": "Value Sets",
"icon": "list",
"badge": "Soon"
}
]
}
];

Roles under the role above them, each badged with how many people hold it, every branch a link to its own record. Collapsible, so a deep branch can be closed — and open on arrival, so the whole shape is visible first.

{
"id": "example",
"section": "A hierarchy that can be put away",
"columns": 1,
"items": [
{
"id": "tree_1",
"type": "component",
"key": "tree",
"inputs": {
"collapsible": true,
"nodes": [
{
"id": "ceo",
"label": "CEO",
"badge": "1 user",
"children": [
{
"id": "ops",
"label": "Ops",
"badge": "No users",
"href": "/setup/roles/ops"
},
{
"id": "sales_vp",
"label": "Sales VP",
"badge": "No users",
"children": [
{
"id": "sales_rep",
"label": "Sales Rep",
"badge": "3 users",
"href": "/setup/roles/sales_rep"
}
]
}
]
}
]
}
}
]
}
<caos-tree id="tree1" collapsible></caos-tree>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const tree1 = document.getElementById('tree1');
tree1.nodes = [
{
"id": "ceo",
"label": "CEO",
"badge": "1 user",
"children": [
{
"id": "ops",
"label": "Ops",
"badge": "No users",
"href": "/setup/roles/ops"
},
{
"id": "sales_vp",
"label": "Sales VP",
"badge": "No users",
"children": [
{
"id": "sales_rep",
"label": "Sales Rep",
"badge": "3 users",
"href": "/setup/roles/sales_rep"
}
]
}
]
}
];