Install
SnapSort consists of SnapSort package itself, the SnapEngine core it’s built on, and the frontend framework bindings (if applicable).
npm install @snap-engine/core @snap-engine/asset-base-svelte @snap-engine/snapsort @snap-engine/snapsort-sveltenpm install @snap-engine/core @snap-engine/asset-base-react @snap-engine/snapsort @snap-engine/snapsort-react react react-domnpm install @snap-engine/core @snap-engine/snapsortSvelteKit / SSR
SnapEngine asset packages publish TypeScript source. SvelteKit must bundle the
framework-agnostic asset packages during SSR instead of externalizing them to
Node. Add them to ssr.noExternal in vite.config.js:
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit()],
ssr: {
noExternal: ["@snap-engine/asset-base", "@snap-engine/snapsort"],
},
});Your First Sortable List
Wrap SnapSort components in an Engine, give a Container a group,
and render one Item per entry. This creates a working drag-and-drop list:
<script lang="ts">
import { Engine } from "@snap-engine/asset-base-svelte";
import { Container, Item } from "@snap-engine/snapsort-svelte";
const tasks = [
{ id: "design", label: "Design" },
{ id: "build", label: "Build" },
{ id: "ship", label: "Ship" },
];
</script>
<Engine id="quick-start">
<Container config={{ direction: "column", groupID: "tasks" }} items={tasks}>
{#snippet entry(task)}
<Item itemId={task.id}>{task.label}</Item>
{/snippet}
</Container>
</Engine>import { Engine, Container, Item } from "@snap-engine/snapsort-react";
const tasks = [
{ id: "design", label: "Design" },
{ id: "build", label: "Build" },
{ id: "ship", label: "Ship" },
];
export function QuickStart() {
return (
<Engine id="quick-start">
<Container config={{ direction: "column", groupID: "tasks" }}>
{tasks.map((task) => (
<Item key={task.id} itemId={task.id}>
{task.label}
</Item>
))}
</Container>
</Engine>
);
}import { Engine } from "@snap-engine/core";
import { CollisionEngine } from "@snap-engine/core/collision";
import { Container, Item } from "@snap-engine/snapsort";
const root = document.querySelector("#quick-start");
const list = root.querySelector("[data-snapsort-container]");
const engine = new Engine();
engine.setCollisionEngine(new CollisionEngine());
engine.assignDom(root);
const container = new Container(engine, null, {
direction: "column",
groupID: "tasks",
});
container.element = list;
for (const element of list.querySelectorAll("[data-item-id]")) {
const item = new Item(engine, container);
item.itemId = element.dataset.itemId;
item.element = element;
}Items can now be dragged to reorder, with FLIP animations included by default. The list on screen updates, but your tasks array does not — wiring drops back into framework state is covered in Session Lifecycle.
Production Checklist
Before treating a list as complete:
- Give every item a stable, unique
itemId; do not use its array index. - Keep the framework array as the source of truth. Update it synchronously in
onItemMove(andonItemSwapwhen using swap mode). - Put containers that exchange items in the same
groupIDand attach identifyingmetadatato each container. - Give the
Engineand sortable container real dimensions.Enginedefaults toheight: 100%,position: relative, andoverflow: visible; passstyleor a class when the parent does not already establish a height. - Render one framework component for each core object. Use
Ghostfor framework-owned flow placeholders instead of wrappingevent.ghostItemin a normalItem.
All wrapper components forward normal div attributes such as class, className, style, id, data-*, ARIA attributes, and event handlers. Consumer styles are merged after the structural defaults, so an explicit style can override them.