Skip to content

FAQ

Does it support touch devices?

Yes. Drag gestures support mouse and touch input. The optional vibration prop on Drag can provide feedback on devices that implement navigator.vibrate().

Does it support SSR?

Yes. Components can be rendered on the server; browser event listeners and DOM access are initialized after mounting on the client. Import the library stylesheet through your application or framework CSS configuration.

Does it support keyboard dragging?

No. Version 3 supports pressing Escape to cancel an active mouse or touch drag, but it does not provide keyboard-driven pickup, movement, or dropping. Applications that require keyboard reordering should provide equivalent controls and appropriate accessibility semantics.

Can this be used with Nuxt?

Yes. The components are SSR-compatible. Add vue-easy-dnd/style.css to your Nuxt CSS configuration or import it once from client application code.

Does it work inside Shadow DOM?

Yes. Open shadow roots are supported for mouse and touch target detection, composed drag movement, Drop event routing, and scroll-parent discovery across the shadow host. Closed shadow roots are not supported.

Drag and drop inside an open Shadow DOM

The source, target, event routing, and scroll-parent lookup all operate inside this isolated shadow root.

View example code

Template

vue
<div
  ref="host"
  class="shadow-dom-demo__host"
  aria-label="Shadow DOM drag-and-drop example"
/>

TypeScript

ts
import { createApp, defineComponent, h, onBeforeUnmount, onMounted, ref } from 'vue';
import { Drag, Drop } from 'vue-easy-dnd';
import type { DnDEventPayload } from 'vue-easy-dnd';

const shadowStyles = `
  :host { display: block; height: 20rem; overflow: auto; border: 2px solid #7c3aed; border-radius: 10px; }
  * { box-sizing: border-box; }
  .content { min-height: 38rem; padding: 1rem; color: #1f2937; background: #faf5ff; font: 15px/1.5 system-ui, sans-serif; }
  .source { display: inline-block; padding: .7rem 1rem; border-radius: 8px; background: #7c3aed; color: white; cursor: grab; user-select: none; }
  .target { display: grid; min-height: 7rem; margin-top: 22rem; place-content: center; border: 2px dashed #7c3aed; border-radius: 9px; text-align: center; }
  .target.drop-in { background: #ede9fe; }
`;
const ShadowContent = defineComponent({
  name: 'ShadowDndContent',
  setup () {
    const result = ref('Drag the purple card, scroll down, and drop it here.');
    const onDrop = (event: DnDEventPayload) => {
      result.value = `Dropped: ${String(event.data)}`;
    };
    return () => h('div', { class: 'content' }, [
      h('style', shadowStyles),
      h(Drag, { type: 'shadow-card', data: 'Shadow DOM card' }, {
        default: () => h('div', { class: 'source' }, 'Drag inside the shadow root')
      }),
      h(Drop, { acceptsType: 'shadow-card', class: 'target', onDrop }, {
        default: () => result.value
      })
    ]);
  }
});

const host = ref<HTMLElement | null>(null);
let shadowApp: ReturnType<typeof createApp> | null = null;
onMounted(() => {
  if (!host.value) return;
  const shadowRoot = host.value.attachShadow({ mode: 'open' });
  const mountPoint = document.createElement('div');
  shadowRoot.appendChild(mountPoint);
  shadowApp = createApp(ShadowContent);
  shadowApp.mount(mountPoint);
});
onBeforeUnmount(() => {
  shadowApp?.unmount();
  shadowApp = null;
});