Tree Select
Hierarchical multi-select with checkbox cascading, mixed (partial) parent state, recursive search that auto-expands matched ancestors, and tag overflow.
Preview
Switch the framework picker (top-right of the panel) to render the same demo live in React, Vue, or Angular — same class names, ARIA, and visual output across all three.
Installation
Sisyphos UI ships unified packages for React, Vue, and Angular. Pick the one that matches your stack — every framework exports the same component classes, ARIA semantics, and CSS tokens.
$ pnpm add @sisyphos-ui/reactThen import the bundled stylesheet once at app entry: import "@sisyphos-ui/react/styles.css";
Usage
import { useState } from "react";
import { TreeSelect } from "@sisyphos-ui/react";
const tree = [
{ id: "1", label: "Engineering", children: [
{ id: "1-1", label: "Web" },
{ id: "1-2", label: "Mobile" },
]},
{ id: "2", label: "Design" },
];
export const Departments = () => {
const [ids, setIds] = useState<string[]>([]);
return <TreeSelect label="Departments" data={tree} value={ids} onChange={setIds} />;
};Searchable
Add `searchable` and `clearable` for dense trees.
Multi select
Pre-fill a selection via `value` — renders compact chip summary with `+N` overflow.
Error state
Inline validation mirrors the other form controls.
API
| Prop | Type | Default | Description |
|---|---|---|---|
| nodes* | TreeNode[] | — | Hierarchical data with `id`, `label`, optional `children` and `disabled`. |
| value | TreeNodeId[] | — | Controlled selected ids. |
| defaultValue | TreeNodeId[] | — | Initial uncontrolled selection. |
| onChange | (ids: TreeNodeId[]) => void | — | Called with the next selection. |
| label | string | — | Field label rendered above the trigger. |
| placeholder | string | — | Trigger placeholder when nothing is selected. |
| triggerLabel | string | — | Override for the trigger's primary text. |
| searchable | boolean | true | Show a search input above the tree. Auto-expands matched ancestors while a term is active. |
| searchPlaceholder | string | "Search…" | Placeholder for the search input. |
| cascade | boolean | true | Toggle a parent node to also toggle its descendants. With `false`, only the node itself is selected. |
| maxTagCount | number | 3 | Show up to this many tags in the trigger; remaining count collapses into `+N`. |
| clearable | boolean | false | Render a `Clear all` button when there is a selection. |
| fullWidth | boolean | false | Stretch the trigger to its container. |
| size | "sm" | "md" | "lg" | "md" | Field height + typography scale. |
| placement | Placement | "bottom-start" | Initial popover placement (auto-flips when needed). |
| error | boolean | false | Marks the field as invalid for styling and ARIA. |
| errorMessage | string | — | Message shown below the field when `error` is true. |
| disabled | boolean | false | Disables interaction. |
| required | boolean | false | Adds the required indicator and ARIA flag. |
The full API including refs, ARIA attributes, and HTML passthroughs lives in the package README on npm.
Accessibility
- Each row is a
role="checkbox"witharia-checkedset to"true","false", or"mixed"for partial parents. - The dropdown uses
role="tree"; expand chevrons exposearia-expanded. - Trigger is wired with
role="combobox"+aria-expandedandaria-haspopup="tree". - Search input is portal-mounted alongside the tree and receives focus on open.
Keyboard interactions
| Key | Action |
|---|---|
| Tab | Moves focus to and inside the picker. |
| EnterthenSpace | Toggles the focused node. |
| Esc | Closes the popover. |