SortableJS Clone Palette Page Builder — Free JS Snippet

SortableJS Shared Groups: Clone from a Palette into a Page Builder · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

What's included

Features

pull: "clone" palette that always keeps its items
put: false and sort: false to make the palette read-only
Canvas that accepts, reorders and releases blocks
Trash zone implemented as a Sortable that removes what it receives
onAdd swaps the draggable chip for the real block markup
Hover highlight for the trash via onMove
Live block counter and Clear button
Page state read straight from the DOM (data-type in order)

About this UI Snippet

SortableJS Shared Groups — HTML, CSS & JavaScript

Screenshot of the SortableJS Shared Groups: Clone from a Palette into a Page Builder snippet rendered live

Drag and drop between lists is rarely symmetrical. A component palette should hand out copies, never lose its own items and refuse anything dropped back onto it. A canvas should accept new items and let them be rearranged. A trash zone should swallow whatever is dropped on it. SortableJS models all of this with the group option: when it is an object rather than a string, its pull and put properties define what each list allows, and that is the whole architecture of a page builder in three small configurations.

The palette uses pull: 'clone' and put: false. Cloning means dragging a block out leaves the original in place and creates a copy in the destination, so the palette always offers all five blocks. put: false means the palette accepts nothing, so blocks cannot be dropped back onto it, and sort: false stops its own items from being reordered. The canvas uses pull: true and put: true: it accepts blocks from the palette and lets its own blocks be moved out to the trash or rearranged. All three lists share the group name "builder", which is what lets items travel between them.

There is one subtlety with cloning: what arrives on the canvas is a copy of the palette chip — the small labelled button — not a rendered block. The onAdd callback fires on the destination when an item lands, and the snippet uses it to look up the block type from the copy's data attribute and replace the chip with the real block markup via replaceWith(). That separation between how a block looks as a draggable tool and how it looks on the page is the standard design in real builders. The same callback updates the block counter.

The trash is the neatest trick: an empty list in the same group with put: true and pull: false. Anything dragged onto it triggers onAdd, where the element is simply removed from the DOM, so deletion needs no special-case dragging code. onMove toggles a highlight while a block hovers over it. Page state is the DOM again, so a save function would serialise data-type attributes in order, and a Clear button resets the canvas. The seeded blocks keep the initial preview from looking empty.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant like Claude to add inline editing of block text, undo and redo of page changes, or export the layout as JSON or HTML.

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 mini page builder with SortableJS 1.15 loaded from a CDN.

Requirements:
- Create three lists sharing group name 'builder': a palette (pull: 'clone', put: false, sort: false), a canvas (pull: true, put: true) and an empty trash zone (put: true, pull: false).
- Palette items are chips with a data-type; in the canvas onAdd, replace the cloned chip with the real block markup for that type and update a block counter.
- The trash zone removes evt.item in onAdd and highlights while a block hovers via onMove.
- Add a Clear button, seed the canvas with three blocks, and use a dashed empty-state message when the canvas is empty.

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

Requires
<div class="pb-app">
  <aside class="pb-side">
    <h3>Blocks</h3>
    <ul class="pb-palette" id="pbPalette"></ul>
    <p class="pb-tip">Drag a block onto the page. Palette blocks are copied, never moved.</p>
  </aside>
  <main class="pb-main">
    <div class="pb-bar"><h3>Page</h3><span id="pbCount">0 blocks</span><button type="button" id="pbClear">Clear</button></div>
    <ul class="pb-canvas" id="pbCanvas" aria-label="Page canvas"></ul>
    <div class="pb-trash" id="pbTrash" aria-label="Drop here to delete">&#128465; Drop here to delete</div>
  </main>
</div>

Step by step

How to Use

  1. 1
    Drag from the paletteDrag Heading, Paragraph, Image, Button or Divider onto the page. The palette keeps its copy.
  2. 2
    Reorder the pageDrag blocks on the canvas up and down to change their order.
  3. 3
    Delete a blockDrag a block down onto the red trash zone. It highlights as you hover and the block disappears on drop.
  4. 4
    Try the paletteDrag a block back onto the palette. It refuses, because put is false.
  5. 5
    Clear the pagePress Clear to remove every block at once.

Real-world uses

Common Use Cases

Page and email builders
Compose layouts from reusable blocks. For a nested structure see the nested sortable tree.
Form builders
Drag field types onto a form canvas and remove them with a drop zone.
Report and dashboard designers
Add widgets from a library to a layout.
Learning group pull and put rules
Three lists with three different behaviours, all from group configuration.

Got questions?

Frequently Asked Questions

Set group: { name: "x", pull: "clone", put: false } on the source list and give the destination the same name.

Set put: false on the source group. Add sort: false to stop it reordering itself.

Handle onAdd on the destination and replace evt.item with the element you want, using the type stored in a data attribute.

Create an empty Sortable in the same group that accepts drops, and remove evt.item in its onAdd handler.

Read the destination list's children in order and collect each one's data-type attribute.

Yes. pull and put can be functions that receive the source and target and return true, false or "clone" for dynamic rules.

Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from SortableJS, so in a framework project install it with npm install sortablejs (or react-sortablejs / vuedraggable) instead of the CDN tag, create it in useEffect / onMounted / ngAfterViewInit, and sync your state from onEnd, and release it with destroy() when the component unmounts.