A type-safe library for building hierarchical drag-and-drop interfaces. The framework-agnostic core (@dnd-block-tree/core) provides tree logic, reducers, and utilities with zero dependencies. Framework adapters for React, Svelte 5, and vanilla JS/TS add drag-and-drop integration on top.
| Package | Version | Description |
|---|---|---|
@dnd-block-tree/core |
Framework-agnostic core — types, collision detection, reducers, tree factory, utilities. Zero dependencies. | |
@dnd-block-tree/react |
React adapter — components, hooks, @dnd-kit/core integration. | |
@dnd-block-tree/svelte |
Svelte 5 adapter — components, runes-based state, @dnd-kit/svelte integration. | |
@dnd-block-tree/vanilla |
Vanilla JS/TS adapter — headless controller + optional default renderer. Zero framework deps. |
Choose the adapter for your framework, or use @dnd-block-tree/core directly for headless tree operations (server-side manipulation, testing, custom integrations).
npm install @dnd-block-tree/core @dnd-block-tree/react @dnd-kit/core @dnd-kit/utilitiesRequires React 18+ and @dnd-kit/core 6+.
npm install @dnd-block-tree/core @dnd-block-tree/svelte @dnd-kit/svelte @dnd-kit/dom svelteRequires Svelte 5.29+, @dnd-kit/svelte 0.3+, and @dnd-kit/dom 0.3+.
npm install @dnd-block-tree/core @dnd-block-tree/vanillaNo framework peer dependencies.
import { BlockTree, type BaseBlock, type BlockRenderers } from '@dnd-block-tree/react'
interface MyBlock extends BaseBlock {
type: 'section' | 'task' | 'note'
title: string
}
const CONTAINER_TYPES = ['section'] as const
const renderers: BlockRenderers<MyBlock, typeof CONTAINER_TYPES> = {
section: (props) => <SectionBlock {...props} />,
task: (props) => <TaskBlock {...props} />,
note: (props) => <NoteBlock {...props} />,
}
function App() {
const [blocks, setBlocks] = useState<MyBlock[]>(initialBlocks)
return (
<BlockTree
blocks={blocks}
renderers={renderers}
containerTypes={CONTAINER_TYPES}
onChange={setBlocks}
/>
)
}<script lang="ts">
import { BlockTree, type BaseBlock } from '@dnd-block-tree/svelte'
let blocks = $state([
{ id: '1', type: 'section', title: 'Tasks', parentId: null, order: 0 },
{ id: '2', type: 'task', title: 'Do something', parentId: '1', order: 0 },
])
</script>
<BlockTree
{blocks}
containerTypes={['section']}
onChange={(b) => blocks = b}
>
{#snippet renderBlock({ block, children, isExpanded, onToggleExpand })}
<div>
{#if onToggleExpand}
<button onclick={onToggleExpand}>{isExpanded ? '▼' : '▶'}</button>
{/if}
{block.title}
{#if children}{@render children()}{/if}
</div>
{/snippet}
</BlockTree>import { createBlockTreeController, createDefaultRenderer, createElement } from '@dnd-block-tree/vanilla'
const controller = createBlockTreeController({
initialBlocks: [
{ id: '1', type: 'folder', label: 'Documents', parentId: null, order: 0 },
{ id: '2', type: 'file', label: 'readme.txt', parentId: '1', order: 0 },
],
containerTypes: ['folder'],
})
const container = document.getElementById('tree')!
createDefaultRenderer(controller, {
container,
renderBlock: (block, ctx) => {
const el = createElement('div', { class: 'block' })
el.textContent = block.label
if (ctx.children) el.appendChild(ctx.children)
return el
},
})
controller.mount(container)- Framework-Agnostic Core — zero-dependency headless core with tree factory, reducers, and utilities (docs)
- Stable Drop Zones — zones render from original positions, not preview state
- Ghost Preview — in-flow semi-transparent preview with accurate layout
- Snapshotted Collision — frozen zone rects prevent layout-shift feedback loops
- Depth-Aware Collision — prefers nested zones at indented cursor levels
- Mobile & Touch — long-press activation, haptic feedback, touch-optimized sensors (docs)
- Keyboard Navigation — arrow keys, Home/End, Enter/Space with ARIA roles (docs)
- Multi-Select Drag — Cmd/Ctrl+Click and Shift+Click with batch drag (docs)
- Undo/Redo — composable
useBlockHistoryhook (docs) - Max Depth — limit nesting via
maxDepthprop (docs) - Move Middleware —
onBeforeMoveto validate, transform, or cancel moves (docs) - Fractional Indexing — CRDT-compatible ordering with
orderingStrategy: 'fractional'(docs) - Serialization —
flatToNested/nestedToFlatconverters (docs) - SSR Compatible —
BlockTreeSSRfor hydration-safe rendering (docs) - Animation — CSS expand/collapse transitions + FLIP reorder animations (docs)
- Virtual Scrolling — windowed rendering for 1000+ blocks (docs)
- Custom Collision Detection — pluggable algorithms with sticky hysteresis (docs)
Full API reference, guides, and examples at blocktree.sandybridge.io.
Check out the live demo with two example use cases:
- Productivity — sections, tasks, and notes with undo/redo, max depth, keyboard nav
- File System — folders and files
- dnd-kit — drag and drop toolkit (React via @dnd-kit/core, Svelte via @dnd-kit/svelte)
- Turborepo — monorepo build system
- React 18+ / React 19
- Svelte 5 (runes,
{@attach},{#snippet}) - TypeScript
MIT