jsTree
A production-tested jQuery plugin for interactive, extensible trees with drag-and-drop, checkboxes, search, and JSON or HTML data sources.
Repository Health
Technical Analysis
jsTree is a long-running jQuery plugin that turns a plain container element into an interactive, keyboard-navigable tree widget. It ships as a small core plus a set of optional plugins, so a project only pays for the behavior it actually uses — drag-and-drop reordering, tri-state checkboxes, right-click context menus, inline create/rename/delete, fuzzy search, sorting, and persisted expand/select state via the state plugin.
Data can be supplied as static HTML, an inline JSON array, or loaded asynchronously per-node via AJAX or a custom callback, which makes it equally usable for a small hard-coded menu or a large, lazily-expanded file/category browser. It has shipped through jQuery’s UMD-era packaging conventions (AMD, CommonJS, and plain global script tag) and is still distributed in parallel to Bower, Component, and Composer alongside npm, reflecting its long history as one of the default answers for “add a tree view” in jQuery-based frontends.
What You Get
- A themable, keyboard-navigable tree widget that binds to any container via
$(el).jstree(options) - Support for HTML, inline JSON, and AJAX/async-loaded (including lazily-expanded and bulk “massload”) data sources
- An opt-in plugin system — checkbox, contextmenu, dnd, search, sort, state, types, unique, wholerow — loaded only when configured
- Drag-and-drop node reordering and cross-tree moves via the dnd plugin
- Tri-state checkboxes with parent/child selection propagation via the checkbox plugin
- Persisted expand/select/scroll state across page loads via the state plugin
- A responsive mobile theme alongside the default and proton themes, all customizable via Less
Common Use Cases
- Admin file/category browsers - rendering a nested folder, category, or permission tree with expand/collapse and lazy AJAX loading of children
- Bulk selection UIs - using the checkbox plugin’s tri-state propagation to let users select/deselect whole subtrees at once
- Reorderable navigation trees - using the dnd plugin to let editors drag pages, menu items, or categories into a new hierarchy
- Searchable large hierarchies - using the search plugin’s fuzzy matching to jump to a node in a tree too large to browse manually
- Session-persisted tree state - using the state plugin so a user’s expanded nodes and selection survive a page reload
Under The Hood
Architecture
A UMD factory wrapper (AMD define, CommonJS module.exports, or a plain global) exposes $.jstree, whose $.jstree.create(el, options) instantiates a $.jstree.core instance and then decorates it by calling tmp.plugin(k, options[k]) once per name in options.plugins, before binding the result to the element via $(el).data('jstree', tmp) and calling init. Each plugin file (jstree.checkbox.js, jstree.dnd.js, jstree.contextmenu.js, jstree.search.js, jstree.sort.js, jstree.state.js, jstree.types.js, jstree.unique.js, jstree.wholerow.js, jstree.massload.js) registers itself in $.jstree.plugins and wraps specific core prototype methods (get_node, redraw_node, and similar), calling through to the previous implementation — a decorator/mixin chain layered onto one shared instance rather than class inheritance. Internally, node data is normalized into an id-keyed model on first load and mutated through explicit operations (create_node, delete_node, move_node, rename_node), with redraw_node re-rendering only the affected subtree instead of the whole tree. Because every plugin patches core methods by name and assumes the shape of that internal model, a change to a core method’s signature or to the node model cascades into every plugin file that wraps it.
Tech Stack
Plain ES5-style JavaScript with a single runtime dependency, jQuery ^3.5.0. The build is assembled by Grunt (gruntfile.js) from the individual src/*.js plugin files wrapped by intro.js/outro.js into the distributed dist/jstree.js, with Less compiled to themed CSS (grunt-contrib-less), JS minified (grunt-contrib-uglify), and linted with JSHint (grunt-contrib-jshint) rather than ESLint. The repo carries manifest files for several now-legacy front-end packaging systems in parallel with package.json — bower.json, component.json, and composer.json — a holdover from its 2009 origin, when npm was not yet the default distribution channel for browser code.
Code Quality
An automated unit test file exists (test/unit/test.js, using QUnit) but is effectively a stub at 10 lines, and a separate test/visual directory implies manual, non-automated visual verification rather than real coverage. There is no TypeScript and no type annotations; naming follows a consistent snake_case convention throughout core and plugins (get_node, is_selected, redraw_node). The only GitHub Actions workflow (npm-publish.yml) publishes to npm on a tagged release — it does not run a test step, so there is no CI test gate on incoming changes.
What Makes It Unique Its differentiator versus comparable tree widgets is the decorator-based plugin system itself: consumers opt into behaviors — checkboxes, context menus, drag-and-drop, search, sort, persisted state, whole-row selection — as independently loadable files layered onto one shared core, rather than a single monolithic component with everything baked in, plus first-class support for both static and lazily-loaded/bulk-fetched data sources through the same API. This is a well-executed but by-now standard architecture for a jQuery-era widget rather than a genuinely novel technical approach.
Used by 2 apps in this directory
Leantime
Productivity · Project Management · Collaboration
Project management built for ADHD, autism, and dyslexia — simple as Trello, powerful as Jira
Yaffa
Invoicing Finance
Self-hosted personal finance app for long-term financial planning with AI-powered transaction parsing and investment tracking.