Docs/Guides

DevTools

A Vue DnD Kit tab in the Vue DevTools and the Nuxt DevTools — a live mini-map, a joystick that drags for real, live option editing and a log of every drag.

@vue-dnd-kit/devtools adds a Vue DnD Kit tab to the Vue DevTools (vite-plugin-vue-devtools) and to the Nuxt DevTools. It shows every list, tree and drop target of the page in a live mini-map, drags any item with a joystick, edits options while the app runs, and logs what each drag did. It works in development only: nothing of it reaches a production build.

The tab is a page of its own, served by your dev server next to your app, so it can't run inside these docs. Three steps set it up.

Install

1. Add the packages

npm i -D @vue-dnd-kit/devtools @vue-dnd-kit/actions

@vue-dnd-kit/actions is a peer dependency: the tab's moves go through it. npm 7+ and pnpm install it by themselves; with Yarn 1, add it.

2. Add the plugin

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueDevTools from 'vite-plugin-vue-devtools';
import { vueDnDKitDevtools } from '@vue-dnd-kit/devtools/vite';

export default defineConfig({
  // Next to the Vue DevTools plugin: the tab joins its sidebar.
  plugins: [vueDevTools(), vueDnDKitDevtools(), vue()],
});

With Vite, the plugin does three things on the dev server (vite serve): it serves the tab at /__vue-dnd-kit-devtools/ (under your base), adds it to the Vue DevTools, and makes the page report to it before your app starts. No app.use() is needed, and builds are untouched.

With Nuxt, the @vue-dnd-kit/nuxt module finds the package, serves the tab, adds it to the Nuxt DevTools and installs the page's side with a client plugin. vueDndKit: { devtools: false } turns it off.

3. Run the app in development

Start the dev server as usual (vite, nuxt dev) and open the app: the tab is ready. Every provider of the page shows in it, whichever app it renders in.

Without the plugins

For a dev server of your own, or a page Vite doesn't serve from an index.html (SSR setups), install the page's side yourself:

main.ts
import { createApp } from 'vue';
import { DnDDevtools } from '@vue-dnd-kit/devtools';
import App from './App.vue';

const app = createApp(App);
app.use(DnDDevtools); // before mount: providers set up earlier aren't seen
app.mount('#app');

Or import @vue-dnd-kit/devtools/auto first in the entry file. Use vueDnDKitDevtools({ inject: false }) when the plugin should serve the tab but you install the page's side yourself; serveTab() from @vue-dnd-kit/devtools/vite is the middleware that serves the tab.

In a production build, app.use(DnDDevtools) and the auto import shrink to almost nothing: every entry point checks process.env.NODE_ENV first, and bundlers drop the rest. On the server they do nothing.

Open it

  • Vue DevTools: Alt + Shift + D, or the Vue button at the bottom of the page, then the Vue DnD Kit tab.
  • Nuxt DevTools: Alt + Shift + D, or the Nuxt button, then Vue DnD Kit (in the sidebar or its overflow menu).
  • A window of its own: open http://localhost:5173/__vue-dnd-kit-devtools/ on your dev server, or the tab's ⧉ button. Handy on a second screen. With several pages open, a select on top chooses the one to watch.
Keep the tab in a window of its own, not in a background browser tab of the same window: the page must stay visible, and a hidden page cancels drags.

The tab at a glance

PartWhat it holds
Top barWhat the drag is doing right now (dragging card-3 → done (accepts)), the page watched, ⌖ Pick, the count of edited options with a reset, ⧉ (a window of its own) and the theme.
Left: the registryEvery provider as a tree: its lists, trees, drop targets and draggables. ☰ shows or hides it.
Middle: Map and LogThe live mini-map of the page, or the log of the drags.
Right: Drive, Options, StateThe joystick and the move buttons, the options of the chosen node, and what the provider knows about it.

Choose a node in the registry or an item on the map, and the right side follows it.

The mini-map

A live card per list, tree and drop target, with copies of their items where they are — the same clones the overlay would show, with the page's CSS. During a drag it marks:

  • the lists and targets that accept the drag (blue) or refuse it (red);
  • the placeholder, and the target under the pointer;
  • the pointer, the overlay's box, and the insertion line of placeholder: 'indicator' lists.

A click chooses an item and highlights it on the page; a double click scrolls the page to it. Use the map when a drop doesn't go where you expect: it shows at a glance which lists take the drag.

Drive: the joystick

Choose an item — on the map, in the registry, or with ⌖ Pick, which takes the next press on a draggable of the page instead of letting it act — and push the joystick's knob. The item is picked up and moves that way, faster the farther the knob is from the center. Let go and it stops; Drop and Cancel end the drag.

It is a real pointer drag of a sensor named devtools, made with the public sensor API: lists sort under it, containers auto-scroll at their edges, and onBeforeDrop and onDrop run as for any drag. Disabled items aren't picked up. A keyboard drag the page started can be steered too.

Items of sortable lists also get buttons: up, down, to the start, to the end, to another list of the group. Each is one keyboard drag of moveItem. Below the joystick, a large copy of what the overlay shows.

Key, in the tabDoes
Arrows or W A S DPush the knob that way, halfway; with Shift, all the way
GPick the chosen item up
Space / EnterDrop
EscapeCancel
PPick on the page

Options

Every option that isn't a callback can be edited while the app runs. Choose the node and open Options:

NodeOptions
ProvideroverlayFit, autoScroll, collision, modifiers (the library's, by name), animation, dropAnimation, animateChanges, activation, keyboard, haptics, copyModifier…
Listdisabled, strategy, placeholder, behavior, multiple, sort, dropOutside, acceptMode, animation, animateChanges, autoScroll, modifiers
Treeplaceholder, indentation, nesting, maxDepth…
Drop targetdisabled, priority, accept, acceptMode
Itemdisabled, keyboardMode, activation

An edit applies at once, to this page only, and is never written to your code: the library reads the new value where it reads the option. It lasts until you reset it (↺ per option, or the reset in the top bar), the provider unmounts or the page reloads. An edited option shows your code's value next to it. Items mounted while an edit is set — rows of a virtual list — get it too.

State

What each provider knows, for the node you chose:

  • the drag session: status, the active ids, items and sensor, the target and whether it accepts, the pointer, the overlay, the sortable group's session and insertion line;
  • a list: its order and its draft; a tree: its rows;
  • an item: its type, data and position; a drop target: its rules.

Items mount and unmount without events: the registry catches up within a second.

Log

One band per drag, in order:

EntryWhat it tells
drag startThe ids, the sensor and whether by touch, the list it comes from.
over, leftEach target entered and left, and whether it accepts.
releaseThe target it was let go on, while onBeforeDrop decides.
onBeforeDropThe verdict: accepted, some accepted, rejected by a hook, refused by the target's accept, and how long the hooks took.
dropThe target; for sortable lists the move, e.result, and whether preventDefault() was called.
cancelWhy: cancelled (Escape, cancel()), rejected by a hook, or refused by the target's accept.

Also auto-scroll on and off, the announcement texts (hidden by default) and, outside drags, animate (changes of a list's items that no drag made). Click an entry for its data. A tab that opens later gets the drags so far.

Debug a drop, step by step

A card won't go into a column. With the tab open:

  1. Reproduce it by hand, or with Drive so your hands stay free for the tab.
  2. Watch the map while the card is over the column. Red means the list refuses the card: look at its group (put) or the target's accept. Blue means it accepts, and the problem comes later.
  3. Read the log of that drag. onBeforeDrop says who said no — a hook, or the target's accept. If the drop went through but your data didn't change, the drop entry says whether preventDefault() was called and what e.result held: a controlled list must apply it itself.
  4. Try a fix live in Options — a looser acceptMode, another collision, placeholder: 'indicator' — then write the one that works into your code.

How it works

Providers report to a hook that the page's side installs. Until a tab watches the page, they are only recorded; then the page listens like any monitor and sends the tab what it shows — every 40 ms during a drag, twice a second otherwise — and stops once no tab has watched for a few seconds. The tab talks to the page over a BroadcastChannel of the page's origin. It changes nothing but what you ask for: the drags, the selection and the option overrides.