Dragula Copy vs Move Lists — Free JS Snippet

Dragula Copy vs Move Lists with Spill Control · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

What's included

Features

copy() predicate: drags starting in the menu copy, others move
accepts() rule that keeps the menu read-only as a destination
removeOnSpill for drag-off-the-list deletion
drop, remove, cancel and cloned events used for feedback
Explanatory log message when a drop is refused
Live order total and empty-state hint from the DOM
Dragula's gu-mirror and gu-transit classes styled for feedback
Tiny, dependency-free, historical library

About this UI Snippet

Dragula Copy vs Move Lists — HTML, CSS & JavaScript

Screenshot of the Dragula Copy vs Move Lists with Spill Control snippet rendered live

Dragula is one of the older names in JavaScript drag and drop, and one of the simplest: hand it an array of containers, and elements can be dragged between them. It predates the current generation of libraries, has no dependencies and is small, which is why it is still found in a great many projects and why it remains a good way to learn the fundamentals. Where other libraries reach for configuration objects nested several levels deep, Dragula's whole behaviour is a handful of plain functions, and this snippet uses four of them to model an ordering screen.

The first job is copy versus move. dragula([menu, order]) alone would move dishes out of the menu, emptying it as the customer chooses. The copy option accepts either true — copy every drag — or a function that receives the element and its source container. Returning source === menu makes drags that start in the menu copy the dish, while drags inside the order list still move, so reordering works normally. That single predicate is the entire difference between a shopping cart and a game of musical chairs.

The second is accepts, which decides whether a container may receive a dropped element. Returning target !== menu makes the menu read-only as a destination, so a dish cannot be dropped back onto it. When a drag is refused, Dragula reverts it and fires a cancel event, which the snippet uses to explain the rule. This is more useful than silently snapping back, because a user who does not understand why a drop failed assumes the interface is broken.

The third is spill behaviour. Dropping an element outside every container is called a spill. By default the drag is cancelled and the element returns; removeOnSpill: true deletes it instead, giving a natural gesture for removal — drag it off the list. Events tell the story: drop reports where an element landed and where it came from, remove fires when a spill deletes it, and cloned exposes both the floating mirror and the copy that will be inserted. The mirror gets Dragula's own gu-mirror class, styled here with a shadow, and the placeholder in the list gets gu-transit. The order total and empty-state hint are recomputed from the DOM after every event.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant like Claude to add quantity badges for repeated dishes, persist the order to storage, or add a keyboard "Add to order" button on each dish.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a menu-to-order drag and drop with Dragula 3.7 loaded from a CDN (script and CSS).

Requirements:
- Create dragula([menu, order], { copy: (el, source) => source === menu, accepts: (el, target) => target !== menu, removeOnSpill: true }).
- Render six dishes with an emoji, name and price in the menu list.
- Handle drop, remove and cancel events to show a status message (including why a drop back onto the menu is refused) and recompute an order total and an empty-state hint from the DOM.
- Style .gu-mirror with a shadow and .gu-transit as the placeholder.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="dg-app">
  <section class="dg-col">
    <h3>Menu <small>copies</small></h3>
    <ul class="dg-list" id="dgMenu"></ul>
  </section>
  <section class="dg-col dg-order">
    <h3>Your order <small id="dgTotal">$0.00</small></h3>
    <ul class="dg-list" id="dgOrder" aria-live="polite"></ul>
    <p class="dg-empty" id="dgEmpty">Drag dishes here</p>
  </section>
  <section class="dg-col dg-bin">
    <h3>Rules</h3>
    <ul class="dg-rules">
      <li><b>Menu &rarr; Order</b> copies the dish.</li>
      <li><b>Reorder</b> inside the order moves it.</li>
      <li><b>Drop outside</b> any list to remove it.</li>
      <li><b>Order &rarr; Menu</b> is refused.</li>
    </ul>
    <div class="dg-log" id="dgLog" role="status">Waiting for a drag...</div>
  </section>
</div>

Step by step

How to Use

  1. 1
    Copy a dishDrag a dish from the menu into Your order. The menu keeps its item — it was copied.
  2. 2
    Reorder the orderDrag dishes within Your order to change their sequence. Inside this list, drags move rather than copy.
  3. 3
    Try to put one backDrag an ordered dish onto the menu. It snaps back and the log explains that the menu refuses drops.
  4. 4
    Remove a dishDrag an ordered dish outside any list and release. It is deleted and the total updates.
  5. 5
    Watch the totalThe total in the heading follows every add, move and removal.

Real-world uses

Common Use Cases

SHOP
Cart and order builders
Copy products into a basket by dragging. For a richer palette-and-canvas example with SortableJS see the shared-group page builder.
Template and field pickers
Copy reusable items from a library into a document.
ADMIN
Assignment tools
Copy team members or resources into projects without depleting the source list.
Learning classic drag-and-drop APIs
A compact tour of Dragula's function-based options and events.

Got questions?

Frequently Asked Questions

Use the copy option. Pass true to copy every drag, or a function such as (el, source) => source === menu to copy only from certain containers.

Use accepts(el, target) and return false for containers that should not receive items.

Dropping an element outside every container. With removeOnSpill: true the element is deleted; with revertOnSpill: true it goes back where it started.

Listen for the cancel event, which reports the element, container and source.

gu-mirror is the floating copy that follows the pointer; gu-transit is the placeholder shown in the list while dragging.

Yes. Dragula supports touch events and uses the same options on mobile browsers.

Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from Dragula, so in a framework project install it with npm install dragula (or ng2-dragula for Angular) instead of the CDN tag, create it in useEffect / onMounted / ngAfterViewInit with the container elements, and release it with drake.destroy() when the component unmounts.