lazy-tree-view
v1.0.2
Published
A lightweight, zero-dependency React tree view with lazy loading, drag & drop, and keyboard navigation.
Downloads
299
Maintainers
Readme
A lightweight, zero-dependency React tree view built for real-world data — where children are loaded on demand, not upfront.
Features
- ⚡ Lazy Loading — Children are fetched on demand when a branch is expanded. Built-in loading, error, and retry states.
- 🔀 Drag & Drop — Reorder nodes with three drop positions (before, inside, after). Validate moves with
canDrop. - ⌨️ Keyboard Navigation — Full WAI-ARIA TreeView pattern: arrow keys, Home/End, Enter/Space.
- 🎮 Imperative API — Control the tree via
ref: add, remove, update, move nodes, replace the entire tree, or read its state. - 🎨 Custom Renderers — Replace branch and item components with your own. Pass extra props via
branchProps/itemProps. - ✨ Animations — Smooth expand/collapse transitions. Configurable duration or opt-out entirely.
- 🔒 Type-safe — Generic types (
BranchProps<T>,BaseNode<T>) for custom data on your nodes.
Installation
pnpm add lazy-tree-view
# or
npm install lazy-tree-viewQuick Start
import { LazyTreeView } from 'lazy-tree-view'
import 'lazy-tree-view/styles.css'
const tree = [
{
id: '1',
name: 'Documents',
children: [
{ id: '2', name: 'Resume.pdf' },
{ id: '3', name: 'Cover Letter.docx' },
],
},
{ id: '4', name: 'README.md' },
]
async function loadChildren(branch) {
const res = await fetch(`/api/branches/${branch.id}/children`)
return res.json()
}
function App() {
return (
<LazyTreeView
initialTree={tree}
loadChildren={loadChildren}
/>
)
}Why lazy-tree-view?
Most tree components load the entire tree upfront. This works for small datasets but breaks down when nodes have hundreds of children or when data lives behind an API.
lazy-tree-view is built around a lazy-first model: branches fetch their children only when expanded, with built-in loading, error, and retry states. You supply a single async function — the component handles the rest.
| | lazy-tree-view | rc-tree | react-arborist | | -------------- | ---------------- | -------------- | -------------- | | Bundle size | ~7.5 kB | ~40 kB | ~20 kB | | Dependencies | 0 | antd ecosystem | 3+ | | Lazy loading | Built-in | Manual | Manual | | Drag & drop | Built-in | Via plugin | Built-in | | Imperative API | Full ref API | Limited | Partial | | TypeScript | First-class | Partial | Yes |
Live Demos
Explore all features and use cases in the interactive Storybook.
Props
| Prop | Type | Default | Description |
| ------------------- | ---------------------------------------------------- | ------------ | ---------------------------------------------------------- |
| initialTree | TreeNode[] | — | Initial tree structure. Only read on mount. |
| loadChildren | (branch: BranchNode) => Promise<TreeNode[]> | — | Async callback to fetch children when a branch expands. |
| allowDragAndDrop | boolean | true | Enable drag & drop reordering. |
| useDragHandle | boolean | false | Require a drag handle instead of dragging the entire node. |
| canDrop | (data: DropData) => boolean | () => true | Validate whether a drop is allowed. |
| dragClassNames | Partial<DragClassNames> | — | Custom CSS classes for drag states. |
| branch | FC<BranchProps> | Built-in | Custom branch renderer. |
| item | FC<BaseNodeProps> | Built-in | Custom item (leaf) renderer. |
| branchProps | Record<string, unknown> | — | Extra props forwarded to every branch. |
| itemProps | Record<string, unknown> | — | Extra props forwarded to every item. |
| disableAnimations | boolean | false | Disable expand/collapse animations. |
| animationDuration | number | 300 | Animation duration in ms. |
| className | string | — | CSS class for the root <ul>. |
| style | CSSProperties | — | Inline styles for the root <ul>. |
| onDrop | (data: DropData) => void | — | Called after a successful drop. |
| onTreeChange | (tree: TreeNode[]) => void | — | Called whenever the tree changes. |
| onLoadStart | (branch: BranchNode) => void | — | Called when a branch starts loading. |
| onLoadSuccess | (branch: BranchNode, children: TreeNode[]) => void | — | Called when children load successfully. |
| onLoadError | (branch: BranchNode, error: unknown) => void | — | Called when loading fails. |
Imperative API
Control the tree from outside via ref:
const treeRef = useRef<LazyTreeViewHandle>(null)
// Add a node to a branch
treeRef.current?.addNode('branch-1', { id: 'new', name: 'New File' })
// Read the current tree
const tree = treeRef.current?.getTree()| Method | Signature | Description |
| ------------ | ---------------------------------------------- | ---------------------------------- |
| addNode | (parentId: string \| null, node) => void | Add a node. null for root level. |
| removeNode | (nodeId: string) => void | Remove a node and its children. |
| updateNode | (nodeId: string, updates) => void | Update node properties. |
| moveNode | (nodeId: string, targetId, position) => void | Move a node programmatically. |
| setTree | (newTree: TreeNode[]) => void | Replace the entire tree. |
| getTree | () => TreeNode[] | Get the current tree. |
| getNode | (nodeId: string) => TreeNode \| undefined | Find a node by ID. |
Custom Renderers
Replace the default branch and item components with your own. The generic <T> lets you type the extra props passed via branchProps / itemProps:
import { LazyTreeView, type BranchProps, type BaseNodeProps } from 'lazy-tree-view'
type BranchExtra = { icon: string }
type ItemExtra = { onSelect: (id: string) => void }
const MyBranch = ({ isOpen, name, icon, onToggleOpen }: BranchProps<BranchExtra>) => (
<div onClick={onToggleOpen}>
{isOpen ? '▾' : '▸'} {icon} {name}
</div>
)
const MyItem = ({ id, name, onSelect }: BaseNodeProps<ItemExtra>) => (
<div onClick={() => onSelect(id)}>📄 {name}</div>
)
<LazyTreeView
initialTree={tree}
loadChildren={loadChildren}
branch={MyBranch}
item={MyItem}
branchProps={{ icon: '📁' }}
itemProps={{ onSelect: (id) => console.log('Selected:', id) }}
/>Keyboard Navigation
Follows the WAI-ARIA TreeView pattern:
| Key | Action |
| --------------- | ------------------------------------- |
| ↑ ↓ | Move focus between visible nodes |
| → | Expand branch, or move to first child |
| ← | Collapse branch, or move to parent |
| Home End | Jump to first / last node |
| Enter Space | Toggle expand / collapse |
Development
pnpm install
pnpm storybook # Development environment
pnpm test # Run tests
pnpm build # Build the library