A React tree component for hand-crafted hierarchical interfaces. Unlike data-driven tree libraries, handtree lets you compose tree structures manually with full control over styling, behavior, and layout.
📖 Live Examples & Documentation →
Most tree components are data-driven - they work well when you can normalize your data into a homogeneous structure like {id, children}. But many real-world scenarios resist this normalization:
// Hard to normalize - different node types, mixed content const jsonSchema = { User: { type: 'object', required: true, properties: { id: { type: 'string', validation: /\d+/ }, profile: { type: 'object', properties: { name: { type: 'string', maxLength: 50 }, settings: { type: 'array', items: 'string' } }} }} }handtree is perfect when you need to:
- Handle complex nested data structures that don't fit a uniform schema
- Different node content and behaviors for different node types (objects vs arrays vs primitives, each with unique styling and interactions)
Instead of forcing your data into a generic tree format, handtree lets you craft each node type exactly as it should appear and behave.
Real-world usage: OpenAPI schema editor where each field type (object, array, string, number) has unique rendering, metadata, and interactions. Note the rich details below each field (descriptions, examples, constraints) - this level of content is difficult and often not worth normalizing into a generic tree structure.
handtree powers the hierarchical interfaces in reapi.com - an OpenAPI schema editor and no-code testing platform. It handles complex nested schemas with hundreds of nodes while maintaining smooth performance and user experience.
npm install handtree # or pnpm add handtree # or yarn add handtreeThe core pattern is using TreeNode as a visual renderer inside your own data-type-oriented components. You don't use TreeNode directly - instead, you create components that understand your specific data structure and use TreeNode to render the tree visualization.
// Your data-oriented component function JsonSchemaNode({ schema, level }) { return ( <TreeNode level={level} expandable={schema.type === 'object'} title={<SchemaTitle data={schema} />} details={<SchemaDetails data={schema} />} > {schema.properties?.map(prop => <JsonSchemaNode key={prop.name} schema={prop} level={level + 1} /> )} </TreeNode> ) } // Your custom title component function SchemaTitle({ data }) { return ( <span className="flex items-center gap-2"> <span className="text-blue-600 font-bold">{data.name}</span> <span className="text-gray-500">:</span> <span style={{ color: getTypeColor(data.type) }}>{data.type}</span> {data.required && <span className="text-xs bg-red-100 text-red-800 px-1 rounded">required</span>} </span> ) } // Your custom details component function SchemaDetails({ data }) { return data.description ? ( <div className="text-xs text-gray-600 italic py-1"> {data.description} {data.validation && <code className="ml-2 bg-gray-100 px-1">{data.validation.toString()}</code>} </div> ) : null } // Usage <TreeContext.Provider value={{ indent: 24, ancestorLastTrail: [], classNames: {} }}> <JsonSchemaNode schema={mySchema} level={0} /> </TreeContext.Provider>This way, JsonSchemaNode handles the business logic (what's expandable, how to render titles), while TreeNode handles the tree visualization (lines, indentation, expand/collapse UI).
Unlike traditional tree components that only show a title per node, handtree supports rich details content below each title. This is perfect for showing:
- Documentation (API descriptions, schema details)
- Metadata (file sizes, timestamps, validation rules)
- Status indicators (health checks, build results)
- Secondary actions (buttons, links, badges)
The details section maintains proper tree indentation and connecting lines, creating a clean hierarchical layout even with complex content.
Provides configuration for the entire tree.
interface ITreeContext { indent: number // Indentation per level (px) ancestorLastTrail: boolean[] // Internal: tracks line drawing classNames: Record<string, string> // Custom CSS classes }The visual renderer component. This handles tree UI concerns (indentation, connecting lines, expand/collapse icons) while you handle the data concerns in your wrapper component.
interface TreeNodeProps { level: number // Nesting level (0 = root) lastNode?: boolean // Is this the last sibling? title: React.ReactNode // Node content (your custom JSX) details?: React.ReactNode // Additional details below title children?: React.ReactNode // Child nodes (usually more of your wrapper components) expandable?: boolean // Can this node be expanded? expanded?: boolean // Is this node expanded? onToggleExpanded?: () => void // Expand/collapse handler }Key responsibilities:
- Visual structure: Draws connecting lines, handles indentation
- Expand/collapse UI: Shows icons and handles click interactions
- Layout: Positions title, details, and children properly
Your wrapper component handles:
- Data interpretation: What should be expandable? What's the title?
- Business logic: State management, event handling
- Content rendering: Custom styling, icons, badges, etc.
Explore interactive examples and see handtree in action:
📖 Live Examples & Documentation →
- Basic Tree View - Simple hierarchical structure showing the core TreeNode API
- Interactive JSON Schema Explorer - Complex nested data with custom styling, expand/collapse state management, and rich details content. Perfect example of handling heterogeneous data structures that resist normalization.
- Minimal Example - Lightweight implementation showing the essential patterns
handtree uses prefixed CSS classes that you can easily override:
/* Customize tree lines color */ .handtree-root { --outline-color: #0066cc; } /* Style the expand/collapse icons */ .handtree-head { color: #666; } .handtree-head:hover { color: #0066cc; } /* Customize spacing */ .handtree-root { --indent-width: 32px; --head-width: 30px; }Available CSS classes:
.handtree-root- Root container.handtree-node- Individual node wrapper.handtree-head- Expand/collapse area.handtree-title- Title content area.handtree-details- Details content area.handtree-content- Main content wrapper.handtree-indent- Indentation guides
CSS Variables:
--outline-color- Tree connecting lines color--indent-width- Indentation per level--head-width- Width of expand/collapse area
# Install dependencies pnpm install # Start development with hot reload pnpm dev # View component demos pnpm ladle:serve # Build for production pnpm buildWe welcome contributions! Please see our contributing guidelines for details.
MIT © [Your Name]