Docyrus

TreeSelect

Trigger field that opens a searchable, flattened tree in a bottom sheet — single or multi select, leaf-only selection and breadcrumb display, API-aligned with the web TreeSelect.

iOSAndroid
Preview TreeSelect on your device

Scan with Expo Go

Download Expo Go, then scan the QR code to preview native components.

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-tree-select
Required Packages(2 packages)
pnpm add @shopify/flash-list tailwind-variants

Usage

import { useState } from 'react';

import { TreeSelect, type TreeSelectValue } from '@/components/docyrus-native/tree-select';
import { type TreeViewItem } from '@/components/docyrus-native/tree-view';

const data: TreeViewItem[] = [
  {
    id: 'eng',
    name: 'Engineering',
    type: 'group',
    children: [
      { id: 'eng-fe', name: 'Frontend', type: 'team' },
      { id: 'eng-be', name: 'Backend', type: 'team' }
    ]
  }
];

export function TeamPicker() {
  const [team, setTeam] = useState<TreeSelectValue>(null);
  const [teams, setTeams] = useState<TreeSelectValue>([]);

  return (
    <>
      <TreeSelect
        data={data}
        value={team}
        onValueChange={setTeam}
        iconMap={{ group: 'fal folder', team: 'fal users' }}
        leafOnly
        showBreadcrumb />

      <TreeSelect
        data={data}
        multiple
        value={teams}
        onValueChange={setTeams}
        sheetTitle="Teams" />
    </>
  );
}

Interaction model

GestureSingleMultiple
Tap triggerOpens the sheet (search + tree)Opens the sheet
Tap leaf rowCommits the id and closesToggles the leaf
Tap folder rowCommits the folder id and closes; with leafOnly it expands / collapses insteadToggles the folder — cascades to the folder id and every descendant, or only to leaf descendants with leafOnly; with leafOnly the row tap expands / collapses and the checkbox cascades
Tap chevronExpands / collapsesExpands / collapses
Tap chip ×—Removes the item (and its cascade)
Done (header)—Closes the sheet

The sheet lists the tree as a flattened FlashList (depth indent, chevrons, tri-state checkboxes in multi mode — a folder shows indeterminate when only some of its leaves are selected), so large trees stay smooth. Searching filters by name and opens every folder on the path to a match. Expansion is re-seeded from defaultExpanded and the search is cleared every time the sheet opens (the web tree remounts per open).

The multi-mode trigger shows up to maxVisibleChips chips and collapses the rest into +N.

API Reference

TreeSelectProps

PropTypeDefaultDescription
dataTreeViewItem[]-Tree data shown in the sheet
valueTreeSelectValue-Selected id (single) or ids (multiple)
onValueChange(value: TreeSelectValue) => void-Selection changed — string in single mode, string[] in multi mode, null when cleared
multiplebooleanfalseMulti-select mode (checkboxes + Done button)
placeholderstringt('ui.treeSelect.placeholder', 'Select...') / t('ui.treeSelect.multiPlaceholder', 'Select items...')Trigger placeholder
disabledbooleanfalseDisable the control
classNamestring-Additional classes for the trigger
onBlur() => void-Called when the sheet closes (native stand-in for the trigger blur)
invalidbooleanfalseError border on the trigger (replaces web aria-invalid)
detent'small' | 'medium' | 'large' | 'fit''large'Sheet height (replaces web maxHeight)
getIcon(item: TreeViewItem, depth: number) => ReactNode-Custom icon renderer (wins over iconMap)
iconMapTreeViewIconMap-item.type → icon; a string renders as a DocyrusIcon identifier
hideInfobooleantrueHide the per-row info caption (type · N direct items) — the native counterpart of the web hover card
searchPlaceholderstringt('ui.treeView.searchPlaceholder', 'Search...')Search placeholder
leafOnlybooleanfalseOnly leaves become values (see the interaction model)
defaultExpanded'none' | 'all' | 'selected' | ReadonlyArray<string>'selected'Folders expanded each time the sheet opens ('selected' = ancestors of the current value)
showBreadcrumbbooleanfalseRender Group › Subgroup › Item in the trigger; ancestors are muted
renderSelected(item: TreeViewItem) => ReactNode-Custom trigger rendering for a selected item (wins over showBreadcrumb; strings are wrapped in Text)
openboolean-Controlled open state
onOpenChange(open: boolean) => void-Open state changed
sheetTitlestringplaceholderSheet title (native addition)
maxVisibleChipsnumber3Multi mode: chips shown before collapsing the rest into +N (native addition)
testIDstring-Test id for the trigger
accessibilityLabelstringplaceholderAccessibility label for the trigger

Differences from web

  • ref, id, name and aria-invalid are dropped — use invalid, testID and accessibilityLabel.
  • maxHeight is replaced by detent.
  • sheetTitle and maxVisibleChips are native additions.

Translation keys

Copy resolves through useUiTranslation() (explicit prop → <UiTranslationProvider> → English fallback). Shared with web: ui.treeSelect.placeholder, ui.treeSelect.multiPlaceholder, ui.treeSelect.removeItem, ui.treeView.searchPlaceholder, ui.treeView.noItemsFound. Native additions: ui.treeSelect.done, ui.treeView.selected, ui.treeView.expand, ui.treeView.collapse, ui.treeView.directItems.

Components

ComponentDescription
TreeSelectTrigger + tree bottom sheet

Type Exports

TypeDescription
TreeSelectPropsProps for TreeSelect
TreeSelectValuestring | string[] | null | undefined
TreeSelectDefaultExpanded'none' | 'all' | 'selected' | ReadonlyArray<string>

On this page