Core Concepts
The fundamental building blocks of MosaicJS.
Basic Drag
This is the simplest possible MosaicJS setup.
It gives you a working drag-and-drop list with:
- zero configuration beyond
root+selectors - automatic ghost creation
- automatic reordering
- safe DOM rollback if needed
Try dragging the items below:
MosaicJS works with three main pieces:
1️⃣ HTML structure Elements with data-mosaic-id so Mosaic can track them.
<div class="container">
<!--
Set up a basic container and items
with unique values in data-mosaic-id
-->
<div id="source">
<div class="item" data-mosaic-id="item-1">Item 1</div>
<div class="item" data-mosaic-id="item-2">Item 2</div>
<div class="item" data-mosaic-id="item-3">Item 3</div>
</div>
</div>2️⃣ CSS Your visuals + Mosaic’s runtime classes like .mosaic--ghost and .mosaic--active.
/** MosaicJS offers no internal CSS. It will work with any framework. */
/** Generic background for iFrame */
body {
font-family: system-ui, -apple-system, sans-serif;
margin: 0;
padding: 32px;
background: #0d1117;
color: #e6edf3;
}
.container {
display: flex;
justify-content: center;
}
/** This element holds the sortable items */
#source {
width: 420px;
padding: 20px;
background: #111826;
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.06);
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.06),
0 14px 40px rgba(0,0,0,0.45);
}
/*
* These are the draggable items.
* Mosaic won't change how they look.
* Style them however you like.
*/
.item {
padding: 14px 16px;
margin-bottom: 10px;
background: #161b28;
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.08);
transition: transform .15s ease,
box-shadow .15s ease,
border .15s ease;
user-select: none;
--webkit-user-select: none;
}
.item:hover {
border: 1px solid rgba(255,255,255,0.25);
transform: translateY(-1px);
}
/*
* .mosaic--ghost is automatically added to
* the cloned element Mosaic creates while dragging.
*
* You don’t create this element;
* Mosaic does.
* You only style it.
*/
.mosaic--ghost {
opacity: 1;
box-shadow:
0 16px 40px rgba(0,0,0,.4),
0 0 0 2px rgba(100,181,246,.6);
cursor: grabbing;
transform: scale(1.02);
will-change: transform;
}
/*
* .mosaic--active is a dynamically added class,
* which finds its way on both the ghost
* and the original element.
*
* In this example we mask its contents to create
* a void to be dropped on.
*/
.mosaic--active:not(.mosaic--ghost){
border: 2px dashed #64b5f6;
background: rgba(100,181,246,.08);
color: transparent;
}
.mosaic--active:not(.mosaic--ghost) * {
opacity: 0;
}3️⃣ JavaScript Create the Mosaic instance → call initialize() → done.
// Import Mosaic from the module.
import { Mosaic } from '/demos/_shared/mosaic.js';
// Set up the configuration of Mosaic.
const mosaic = new Mosaic({
root: document.getElementById('source'),
selectors: {
node: '.item'
}
});
// Initialize to get it working for you.
mosaic.initialize();CSS Class Customization
MosaicJS attaches CSS classes to elements while dragging.
But you're not locked to the default:
mosaic--activemosaic--ghostmosaic--drop-target
You can rename them to match your design system.
This example replaces them with custom names.
HTML
<div class="wrap">
<div id="list">
<div class="item" data-mosaic-id="1">One</div>
<div class="item" data-mosaic-id="2">Two</div>
<div class="item" data-mosaic-id="3">Three</div>
</div>
</div>CSS (custom class names, not Mosaic defaults)
body {
background: #0d1117;
color: #e6edf3;
padding: 32px;
font-family: system-ui, -apple-system, sans-serif;
}
.wrap { display: flex; justify-content: center; }
#list {
width: 340px;
padding: 16px;
background: #111826;
border-radius: 10px;
border: 1px solid rgba(255,255,255,.1);
}
.item {
background: #161b28;
border: 1px solid rgba(255,255,255,.12);
padding: 12px 14px;
margin-bottom: 8px;
border-radius: 6px;
user-select: none;
}
/* -------------------------
Custom drag class contract
-------------------------- */
.drag-active:not(.drag-ghost) {
border: 2px dashed #ffd166;
background: rgba(255,209,102,.12);
}
.drag-ghost {
cursor: grabbing;
transform: scale(1.02);
opacity: 1;
box-shadow:
0 14px 40px rgba(0,0,0,.45),
0 0 0 2px rgba(255,209,102,.6);
}
.drag-target {
outline: 2px dashed rgba(255,255,255,.3);
}JavaScript (overriding Mosaic's class contract)
import { Mosaic } from '/demos/_shared/mosaic.js';
const mosaic = new Mosaic({
root: document.getElementById('list'),
selectors: {
node: '.item'
},
cssClasses: {
active: 'drag-active',
ghost: 'drag-ghost',
dropTarget: 'drag-target'
}
});
mosaic.initialize();Event Handling
MosaicJS is event-driven. This example shows how to listen for lifecycle events emitted during drag, mutation, and rollback.
Try dragging and dropping the items. Watch the event log on the right.
What This Demonstrates
Mosaic emits global events such as:
mosaic:initmosaic:statemosaic:mutation:confirmedmosaic:mutation:rejectedmosaic:rollback
You can respond without framework bindings.
Events are informative and predictable.
import { Mosaic } from '/demos/_shared/mosaic.js';
const logList = document.getElementById('events');
function log(name, detail = null) {
const li = document.createElement('li');
li.innerHTML =
`<strong>${name}</strong>` +
(detail ? ` <span>${JSON.stringify(detail)}</span>` : '');
logList.prepend(li);
while (logList.children.length > 12)
logList.removeChild(logList.lastChild);
}
const mosaic = new Mosaic({
root: document.getElementById('source'),
selectors: { node: '.item' }
});
mosaic.initialize();
/* Event listeners */
window.addEventListener('mosaic:init', () => {
log('mosaic:init');
});
window.addEventListener('mosaic:state', e => {
log('mosaic:state', e.detail);
});
window.addEventListener('mosaic:mutation:confirmed', () => {
log('mosaic:mutation:confirmed');
});
window.addEventListener('mosaic:mutation:rejected', () => {
log('mosaic:mutation:rejected');
});
window.addEventListener('mosaic:rollback', () => {
log('mosaic:rollback');
});