Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

naivefun/handtree

Repository files navigation

handtree

npm version npm downloads bundle size TypeScript License: MIT

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 β†’

Why handtree?

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:

  1. Handle complex nested data structures that don't fit a uniform schema
  2. 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.

OpenAPI Schema Editor built with handtree

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.

Used in Production

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.

Installation

npm install handtree
# or
pnpm add handtree
# or 
yarn add handtree

How it works

The 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).

Beyond Title: Rich Details Support

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.

API Reference

TreeContext

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
}

TreeNode

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.

Examples

Explore interactive examples and see handtree in action:

πŸ“– Live Examples & Documentation β†’

Available Examples

  • 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

Customizing Styles

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

Development

# Install dependencies 
pnpm install
# Start development with hot reload
pnpm dev
# View component demos
pnpm ladle:serve
# Build for production
pnpm build

Contributing

We welcome contributions! Please see our contributing guidelines for details.

License

MIT Β© [Your Name]

About

A React tree component for hand-crafted hierarchical interfaces

Topics

Resources

Stars

Watchers

Forks

Packages

Contributors

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /