Build Better UIs: A Framework-Agnostic Tree View with Drag & Drop for Vue (and Beyond)




Taming Hierarchy in the UI Jungle

Tree views are everywhere—navigation menus, page builders, file explorers, site maps.

But if you’ve ever tried building one from scratch, you know it’s deceptively complex:

Most solutions are tightly bound to a framework (React or Vue), often fragile under large datasets, or simply not extensible.

I wanted something different.
So I built Node Explorer—a web component that solves all of this while staying framework-agnostic and fully customizable.




Introducing: Node Explorer

Node Explorer is a standalone, modern web component that offers:

  • Infinite nesting of tree nodes
  • Native drag-and-drop support
  • API event system for control (select, expand, drop, etc.)
  • Material Design icon support
  • Accessible by keyboard
  • Fully themeable (dark, light, minimal, high contrast)
  • Works with React, Vue, Angular, or plain JavaScript

And it’s open source.
Let me show you how to use it in a real Vue 3 + TypeScript project.




Installation

npm install @jmkcoder/components
Enter fullscreen mode

Exit fullscreen mode




Vue 3 + TypeScript Integration

Take your content tree or CMS editor to the next level with a native-feeling file explorer in just a few lines of code.

Here’s what the result looks like when embedded in your app

Image description

Lets break it down




📦 Step 1: Import the Component

Lets import the styles and component bundle in your Vue file

import '@jmkcoder/components/dist/components.bundle.css'
import '@jmkcoder/components/dist/index.js'
Enter fullscreen mode

Exit fullscreen mode




🧩 Step 2: Wrap in a Local Component

Use the odyssey-node-explorer as a native Web Component inside a Vue wrapper

<!-- src/components/NodeExplorer.vue -->
<template>
  <odyssey-node-explorer
    ref="explorer"
    :allow-drag-drop="allowDragDrop"
    :allow-multi-select="allowMultiSelect"
    :theme="theme"
    :compact="compact"
  />
</template>

<script lang="ts">
import '@jmkcoder/components/dist/components.bundle.css'
import '@jmkcoder/components/dist/index.js'
import  defineComponent, ref, watch, onMounted, onBeforeUnmount  from 'vue'

export default defineComponent(
  name: 'NodeExplorer',
  props: 
    nodes: Array,
    allowDragDrop:  type: Boolean, default: true ,
    allowMultiSelect:  type: Boolean, default: false ,
    theme:  type: String, default: 'light' ,
    compact:  type: Boolean, default: false ,
  ,
  emits: ['node-selected'],
  setup(props,  emit )  null>(null)

    const updateNodes = () => 
      if (explorer.value) 
        explorer.value.setAttribute('nodes', JSON.stringify(props.nodes))
      
    

    const handleNodeSelected = (event: CustomEvent) => 
      emit('node-selected', event)
    

    watch(() => props.nodes, updateNodes)
    watch(() => props.compact, (newVal) => 
      explorer.value?.setAttribute('compact', newVal ? 'true' : 'false')
    )

    onMounted(() => 
      updateNodes()
      explorer.value?.addEventListener('node-selected', handleNodeSelected)
    )

    onBeforeUnmount(() => 
      explorer.value?.removeEventListener('node-selected', handleNodeSelected)
    )

    return  explorer 
  ,
)
</script>
Enter fullscreen mode

Exit fullscreen mode




🏡 Step 3: Use It in a View

Pass your dynamic data into the explorer with simple bindings.

<!-- src/views/HomeView.vue -->
<template>
  <NodeExplorer
    :nodes="treeData"
    theme="dark"
    @node-selected="onNodeSelected"
  />
</template>

<script setup lang="ts">
import NodeExplorer from '../components/NodeExplorer.vue'
import type  ExplorerNode  from '@jmkcoder/components/dist/components/node-explorer/node-explorer.type'

const treeData: ExplorerNode[] = [
  
    id: 'root',
    label: 'My Project',
    icon: 'folder',
    expanded: true,
    children: [
      
        id: 'src',
        label: 'src',
        icon: 'folder',
        children: [
          
            id: 'components',
            label: 'components',
            icon: 'folder',
            children: [
               id: 'button.js', label: 'button.js', icon: 'javascript' ,
               id: 'input.js', label: 'input.js', icon: 'javascript' ,
            ],
          ,
           id: 'app.js', label: 'app.js', icon: 'javascript' ,
        ],
      ,
    ],
  ,
]

const onNodeSelected = (event: CustomEvent) => 
  const selectedNode = event.detail.node as ExplorerNode
  console.log('Selected:', selectedNode.label)

</script>
Enter fullscreen mode

Exit fullscreen mode




🎨 Customizing the Look

Switch between light/dark themes instantly:

<odyssey-node-explorer theme="dark" />
Enter fullscreen mode

Exit fullscreen mode

Or go fully custom using CSS variables:

odyssey-node-explorer 
  --node-label-color: #4caf50;
  --node-hover-bg: rgba(76, 175, 80, 0.1);

Enter fullscreen mode

Exit fullscreen mode




API Events You Can Hook Into

Event Name Description
load-children Fired when a lazy-loaded node is expanded and needs its children
node-selected Fired when a node is selected
nodes-selected Fired when multiple nodes are selected (when multi-select is enabled)
nodes-changed Fired when nodes structure changes (drag-drop, expand/collapse)
node-expanded Fired when a node is expanded
node-collapsed Fired when a node is collapsed
drag-start Fired when a drag operation starts
drag-over Fired during a drag operation
drag-leave Fired when a drag operation leaves a target
drop Fired when a node is dropped

These events give you fine-grained control over how you react to user actions, whether you’re syncing state to Vuex, calling an API, or just updating UI.




Why Node Explorer is Different

There are many libraries and plugins that try to solve this.

Node Explorer does it while being:

  • Framework-agnostic: works with anything
  • Composable: expose only what you need via API
  • Extensible: add custom data, event handlers, even logic layers
  • Production-ready: optimized rendering and accessibility



Getting Started




Let’s Build Together

This is just the beginning.
Node Explorer will be powering more plugins, editors, and UIs as I work on a full headless CMS platform.

If you’re building something that requires dynamic hierarchy—give this component a try.
If it sparks ideas or could use improvements, I’d love your feedback.

Star it. Fork it. Use it.
Let’s explore what’s possible, together.



Source link