A field that picks from nested options: province, city and district, or a product's category, shown as its whole path.
$ pnpm dlx shadcn@latest add @dashral/tree-selectInstallation
$ pnpm dlx shadcn@latest add @dashral/tree-selectAdds the component, the components it builds on, its npm packages and the theme tokens it uses. Or by its full URL: https://dashral.com/r/tree-select.json
When to use
- Picking one item from a hierarchy in a form: province, city and district for an address, a product's category, a team inside a department.
- Several items across a hierarchy, with multiple: the areas a courier covers, the categories a discount applies to.
When not to use
- A flat list of options: Select, or Combobox to search it.
- A hierarchy people browse and work in, not pick from: Tree View on the page.
Usage
selectable="leaf" lets only the deepest level be picked; the field shows the whole path.
"use client"
import { TreeSelect } from "@/components/dashral/tree-select"
export default function DeliveryArea() {
return (
<TreeSelect
label="Delivery area"
selectable="leaf"
items={[
{
id: "west-java",
label: "West Java",
children: [
{ id: "bandung", label: "Bandung", children: [{ id: "coblong", label: "Coblong" }, { id: "sukajadi", label: "Sukajadi" }] },
{ id: "bogor", label: "Bogor", children: [{ id: "bogor-tengah", label: "Bogor Tengah" }] },
],
},
]}
onValueChange={(district) => console.log(district)}
/>
)
}Using the npm package? Import from @dashral/ui/components/tree-select instead, here and in the examples; the rest is the same.
Examples
A product's category
Any level can be picked: a branch's label picks it, its arrow only opens it. Icons on the top level, a disabled item, and picking the current one again clears it.
API reference
TreeSelect
| Prop | Type | Default |
|---|---|---|
items | TreeItem[]The nested options, as Tree View takes them: id, label, icon, children, disabled. Required. | — |
value / defaultValue | string | nullThe picked item's id, controlled or initial. | null |
onValueChange | (value: string | null) => voidThe new id, or null when it's cleared. | — |
selectable | "any" | "leaf""any": a branch's label picks it and its arrow opens it. "leaf": only items without children can be picked; a branch only opens. | "any" |
multiple | booleanA checkbox on every row. value is then string[] (the ticked items without children), and what's ticked shows as chips. | false |
maxShown | numberWith multiple: chips shown before the rest collapse into “+N”. | 3 |
placeholder / searchPlaceholder | string | — |
emptyText | stringWhen the search matches nothing. | "No matches" |
searchable | booleanA search box above the tree. | true |
defaultExpanded | string[] | "all"The branches open the first time; the ones leading to the value open every time. | — |
name | stringSends the value with a form: one hidden input, or one per value with multiple. | — |
disabled / required / id / aria-label | — | |
label | ReactNodeShown above, linked to the control. | — |
description | ReactNodeA hint under the control. | — |
error | boolean | ReactNodetrue shows the error style; a message also shows under the control. | — |
labelAction | ReactNodeAt the end of the label's row, e.g. a “Forgot password?” link or the current value. | — |
withAsterisk | booleanMarks the label with an asterisk, e.g. alongside required. | false |
classNames | { root, label, description, error, trigger, content, tree }Classes for each part. | — |
className | stringExtra classes, merged with cn() so yours win. | — |
Keyboard
| Keys | Action |
|---|---|
| EnterorSpaceor↓ | On the field: open the list |
| Type | In the search: narrow the tree, with the branches leading to a match open |
| ↓ | From the search into the tree |
| ↑ ↓orHomeorEnd | Move between rows |
| →or← | Open a branch or go into it; close it or go up to its parent |
| EnterorSpace | Pick the row (or tick it with multiple); with selectable="leaf", open or close a branch |
| Escape | Close the list and return to the field |
| Backspace | With multiple, on the field: remove the last chip |
Accessibility
- The field is a combobox that opens a tree (the WAI-ARIA tree pattern, from Tree View); the path is read with commas between its steps.
- Each chip's remove button names what it removes; with multiple, the field says how many are picked.
- With label, description or error, the id, aria-describedby and aria-invalid are wired up for you.
Motion
- The list opens as Popover does; branches slide open and closed; chips scale in as they're added.