Docyrus

TreeView

Hierarchical tree with search, selection, checkboxes, long-press actions and drag-and-drop reordering — API-aligned with the web TreeView.

iOSAndroid
Preview TreeView 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-view
Required Packages(3 packages)
pnpm add react-native-gesture-handler react-native-reanimated tailwind-variants

Usage

import { useState } from 'react';

import {
  TreeView,
  type TreeViewItem,
  type TreeViewMenuItem
} from '@/components/docyrus-native/tree-view';

const menuItems: TreeViewMenuItem[] = [
  { id: 'open', label: 'Open', icon: 'fal eye', action: items => console.log(items) }
];

export function FilesTree({ initial }: { initial: TreeViewItem[] }) {
  const [data, setData] = useState(initial);

  return (
    <TreeView
      data={data}
      iconMap={{ folder: 'fal folder', file: 'fal file-lines' }}
      defaultExpandedIds={['root']}
      showCheckboxes
      draggable
      maxHeight={420}
      menuItems={menuItems}
      onSelectionChange={items => console.log(items.map(i => i.id))}
      onCheckChange={(item, checked) => setData(prev => setChecked(prev, item.id, checked))}
      onDataChange={nextData => setData(nextData)} />
  );
}

Interaction model

GestureResult
Tap rowFires onItemClick; replaces the selection (toggles it with multiSelect); a folder expands when it is already selected, when expandFolderOnRowClick is set, or when selectOnRowClick is false
Tap chevronExpands / collapses the folder without selecting it
Tap checkboxFires onCheckChange(item, checked) — the tree reads item.checked from data, so the host applies the change (same contract as web)
Long-press (non-draggable)Opens the row action sheet: menuItems, Select / Deselect (adds to the selection — the mobile stand-in for ⌘/Ctrl-click), Move to… (when draggable), Details (when showInfo)
Long-press + drag (draggable)Lifts the row; dragging over rows shows a before / after line or an inside highlight (inside only when canDropInside allows, never into the item's own subtree). Hovering inside a collapsed folder opens it; the tree's own scroller (with maxHeight) auto-scrolls at its edges. Releasing below the last row moves the item to the root. Drop calls moveTreeViewItem and fires onDataChange
Long-press + release without moving (draggable)Opens the row action sheet

Move to… is the accessible fallback for drag and drop: it lists every valid target (the moved item and its descendants are excluded) plus Top level, and offers Before / Inside / After for the picked target. It fires the same onDataChange(nextData, details) contract. Drag is disabled while a search query is active (web parity).

onSelectionChange is emitted only from user interaction (tap, action sheet, clear) — never because a callback identity or data changed — so inline handlers cannot cause update loops.

API Reference

TreeViewProps

PropTypeDefaultDescription
dataTreeViewItem[]-Tree data. The tree keeps an internal copy (so drag moves apply immediately) that is re-seeded whenever data changes
variant'default' | 'outline' | 'ghost''default'Container style
size'sm' | 'default' | 'lg''default'Row height and text size
showExpandAllbooleantrueShow the expand / collapse all toggle next to the search input
showCheckboxesbooleanfalseShow checkboxes next to items (leaf state from item.checked, folder state derived from children incl. indeterminate)
checkboxPosition'left' | 'right''left'Checkbox position
searchPlaceholderstringt('ui.treeView.searchPlaceholder', 'Search...')Search input placeholder
selectionTextstringt('ui.treeView.selected', 'selected')Text after the selection count ("3 selected")
checkboxLabels{ check: string; uncheck: string }{ check: 'Check', uncheck: 'Uncheck' } (translated)Labels of the bulk check / uncheck buttons shown in the selection toolbar when showCheckboxes
getIcon(item: TreeViewItem, depth: number) => ReactNode-Custom icon renderer (wins over iconMap)
iconMapTreeViewIconMap{}item.type → icon. A string renders as a DocyrusIcon identifier ('fal folder'), any other node renders as-is
onSelectionChange(selectedItems: TreeViewItem[]) => void-Selection changed (items in tree order)
onCheckChange(item: TreeViewItem, checked: boolean) => void-A checkbox or bulk check / uncheck changed an item (bulk calls it for every selected item and its descendants)
menuItemsTreeViewMenuItem[]-Long-press action sheet items
onAction(action: string, items: TreeViewItem[]) => void-Fired after a menu item's action with the menu item id
draggablebooleanfalseEnable long-press drag reorder / re-parenting and the "Move to…" action
canDropInside(item: TreeViewItem) => booleancanTreeViewItemAcceptChildrenWhether an item can accept children (default: it has a children array)
onDataChange(nextData: TreeViewItem[], details: TreeViewMoveDetails) => void-Tree changed by drag or "Move to…"
showInfobooleantrueOffer a Details entry (type, id, location path, direct item count) in the long-press sheet
maxHeightnumber | string-Max height before the tree scrolls in its own scroller — 420, '420px' or '60%'
defaultExpandedIdsReadonlyArray<string>-Ids expanded on mount (uncontrolled — later changes do not re-seed)
expandFolderOnRowClickbooleanfalseTapping a folder row toggles it on the first tap, not only when it is already selected
onItemClick(item: TreeViewItem) => void-Row tapped — always fired, independent of selectOnRowClick
selectOnRowClickbooleantrueWhen false, a row tap never changes the selection (picker mode) — it only expands folders and fires onItemClick; the Select entry is hidden
multiSelectbooleanfalseNative adaptation of web's ⌘/Ctrl-click: a row tap toggles the row in the selection instead of replacing it
longPressDelaynumber400Long-press duration (ms) before a row lifts for dragging / the action sheet opens
classNamestring-Additional classes for the root container
testIDstring-Test id for the root container

TreeViewItem

FieldTypeDefaultDescription
idstring-Unique identifier
namestring-Display name (also searched)
typestring-Item type — keys iconMap, shown in Details
childrenTreeViewItem[]-Child items. An array (even empty) makes the item a folder
checkedboolean-Checked state for leaves (folders derive theirs)
draggableboolean-false excludes the item from drag / "Move to…"
[key: string]unknown-Any extra payload

TreeViewMenuItem

FieldTypeDescription
idstringAction id passed to onAction
labelstringAction label
iconReactNodeA DocyrusIcon string ('fal eye') is shown in the action sheet; other nodes are ignored on native
action(items: TreeViewItem[]) => voidReceives the whole selection when the long-pressed row is selected, otherwise just that row

TreeViewMoveDetails

FieldTypeDescription
activeItemTreeViewItemThe moved item
overItemTreeViewItem | nullThe target item (null for root)
parentItemTreeViewItem | nullThe new parent (null at root level)
positionTreeViewDropPosition'before' | 'inside' | 'after' | 'root'

Helpers

Pure helpers synced byte-for-byte with web @docyrus/ui — use them to apply moves or validate targets outside the component.

ExportSignatureDescription
moveTreeViewItem(items, { activeId, overId, position, canDropInside? }) => { nextData, details } | nullImmutable move; returns null for invalid moves (onto itself, into its own subtree, inside a non-container)
canTreeViewItemAcceptChildren(item) => booleanDefault canDropInside — true when children is an array
treeItemContainsId(item, targetId) => booleantrue when targetId is the item or one of its descendants
treeViewVariantstv() → stringRoot container classes (variant, size)

Translation keys

Copy resolves through useUiTranslation() (explicit prop → <UiTranslationProvider> → English fallback). Shared with web: ui.treeView.searchPlaceholder, ui.treeView.selected, ui.treeView.check, ui.treeView.uncheck, ui.treeView.clearSelection, ui.treeView.noItemsFound. Native additions: ui.treeView.expandAll, ui.treeView.collapseAll, ui.treeView.clearSearch, ui.treeView.select, ui.treeView.deselect, ui.treeView.moveTo, ui.treeView.moveToRoot, ui.treeView.moveBefore, ui.treeView.moveInside, ui.treeView.moveAfter, ui.treeView.details, ui.treeView.close, ui.treeView.infoType, ui.treeView.infoId, ui.treeView.infoLocation, ui.treeView.infoItems, ui.treeView.directItems.

Components

ComponentDescription
TreeViewMain component

Type Exports

TypeDescription
TreeViewPropsProps for TreeView
TreeViewItemTree node
TreeViewIconMapitem.type → icon map
TreeViewMenuItemLong-press action
TreeViewMoveDetailsPayload of onDataChange
TreeViewDropPosition'before' | 'inside' | 'after' | 'root'
TreeViewCheckboxPosition'left' | 'right'
TreeViewVariant'default' | 'outline' | 'ghost'
TreeViewSize'sm' | 'default' | 'lg'

On this page