npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

solidjs-dnd

v0.0.1

Published

A SolidJS-native drag and drop library

Readme

solidjs-dnd

A SolidJS-native drag-and-drop library built on fine-grained reactivity. Primitives-based API with support for sortable lists, grids, multi-container (Kanban), and tree sorting with nesting.

Install

npm install solidjs-dnd solid-js
# or
pnpm add solidjs-dnd solid-js

solid-js ^1.9.0 is a peer dependency.

Quick start

import {
  DragDropProvider,
  createDraggable,
  createDroppable,
  type DragEvent,
} from "solidjs-dnd";

function DraggableItem() {
  const draggable = createDraggable({ id: "item-1" });
  return (
    <div
      ref={draggable.ref}
      style={{
        transform: draggable.isDragging()
          ? `translate3d(${draggable.transform().x}px, ${draggable.transform().y}px, 0)`
          : undefined,
      }}
    >
      Drag me
    </div>
  );
}

function DropZone() {
  const droppable = createDroppable({ id: "zone-1" });
  return (
    <div ref={droppable.ref} classList={{ over: droppable.isOver() }}>
      Drop here
    </div>
  );
}

function App() {
  function handleDragEnd(event: DragEvent) {
    if (event.over) {
      console.log(`Dropped ${event.active.id} on ${event.over.id}`);
    }
  }

  return (
    <DragDropProvider onDragEnd={handleDragEnd}>
      <DraggableItem />
      <DropZone />
    </DragDropProvider>
  );
}

Features

Sortable lists

Vertical, horizontal, or grid sorting with displacement-based animations. Items shift visually during drag; the array only reorders on drop.

import {
  DragDropProvider,
  SortableContext,
  createSortable,
  moveArrayItem,
  closestCenter,
  verticalListStrategy, // or horizontalListStrategy, createGridStrategy
  type DragEvent,
} from "solidjs-dnd";

function SortableItem(props: { id: string; label: string }) {
  const sortable = createSortable({ id: props.id });
  const t = () => sortable.transform();

  return (
    <div
      ref={sortable.ref}
      style={{
        transform: t().x || t().y
          ? `translate3d(${t().x}px, ${t().y}px, 0)`
          : undefined,
        transition: sortable.isDragging() || sortable.isSettling()
          ? "none"
          : "transform 200ms ease",
      }}
    >
      {props.label}
    </div>
  );
}

function SortableList() {
  const [items, setItems] = createSignal([
    { id: "1", label: "First" },
    { id: "2", label: "Second" },
    { id: "3", label: "Third" },
  ]);

  function handleDragEnd(event: DragEvent) {
    if (!event.over) return;
    const from = items().findIndex((i) => i.id === event.active.id);
    const to = items().findIndex((i) => i.id === event.over!.id);
    if (from !== to) setItems((prev) => moveArrayItem(prev, from, to));
  }

  return (
    <DragDropProvider collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
      <SortableContext ids={items().map((i) => i.id)} strategy={verticalListStrategy}>
        <For each={items()}>
          {(item) => <SortableItem id={item.id} label={item.label} />}
        </For>
      </SortableContext>
    </DragDropProvider>
  );
}

Grid sorting

import { createGridStrategy } from "solidjs-dnd";

// Auto-detects columns from item positions
const gridStrategy = createGridStrategy();

// Or specify columns explicitly
const gridStrategy = createGridStrategy({ columns: 4 });

<SortableContext ids={ids()} strategy={gridStrategy}>
  {/* grid items */}
</SortableContext>

Multi-container (Kanban)

Use closestToPointer for nested droppables. Each container gets its own SortableContext with a containerId. Use transferArrayItem to move items between containers.

import {
  DragDropProvider,
  SortableContext,
  createSortable,
  createDroppable,
  closestToPointer,
  moveArrayItem,
  transferArrayItem,
} from "solidjs-dnd";

function KanbanColumn(props: { id: string; items: Item[] }) {
  const droppable = createDroppable({ id: props.id });
  const ids = () => props.items.map((i) => i.id);

  return (
    <div ref={droppable.ref}>
      <SortableContext ids={ids()} containerId={props.id}>
        <For each={props.items}>
          {(item) => <SortableCard item={item} />}
        </For>
      </SortableContext>
    </div>
  );
}

Tree sorting

Drag to reorder and drag left/right to change nesting depth. Subtrees move as a unit.

import {
  DragDropProvider,
  TreeSortableContext,
  createTreeSortable,
  moveTreeItems,
  closestCenter,
  type TreeItem,
} from "solidjs-dnd";

interface Block extends TreeItem {
  label: string;
}

function TreeItem(props: { item: Block }) {
  const sortable = createTreeSortable({ id: props.item.id });
  const t = () => sortable.transform();

  return (
    <div
      ref={sortable.ref}
      style={{
        "margin-left": `${sortable.projectedDepth() * 24}px`,
        transform: t().x || t().y
          ? `translate3d(${t().x}px, ${t().y}px, 0)`
          : undefined,
        transition: sortable.isDragging() || sortable.isSettling()
          ? "none"
          : "transform 200ms ease, margin-left 200ms ease",
        opacity: sortable.isInActiveSubtree() ? 0.3 : 1,
      }}
    >
      {props.item.label}
    </div>
  );
}

function TreeEditor() {
  const [items, setItems] = createSignal<Block[]>([
    { id: "1", depth: 0, label: "Introduction" },
    { id: "2", depth: 1, label: "Getting Started" },
    { id: "3", depth: 0, label: "Advanced" },
  ]);

  // Capture tree context for projection data
  let treeCtx;

  return (
    <DragDropProvider
      collisionDetection={closestCenter}
      onDragEnd={(event) => {
        const proj = treeCtx?.projection();
        if (proj) setItems((prev) => moveTreeItems(prev, event.active.id, proj));
      }}
    >
      <TreeSortableContext items={items()} indentSize={24}>
        {/* Capture context ref */}
        {(() => { treeCtx = useTreeSortableContext(); return null; })()}
        <For each={items()}>
          {(item) => <TreeItem item={item} />}
        </For>
      </TreeSortableContext>
    </DragDropProvider>
  );
}

Drag overlay

Render a floating copy of the dragged item above all content. The original item can be hidden or dimmed.

import { DragOverlay } from "solidjs-dnd";

<DragDropProvider onDragEnd={handleDragEnd}>
  <MyList />
  <DragOverlay dropAnimation dropAnimationDuration={200}>
    {(active) => <div class="floating-card">{active.data.label}</div>}
  </DragOverlay>
</DragDropProvider>

Drag handles

Use activatorRef to restrict drag activation to a handle element.

const sortable = createSortable({ id: "item-1" });

<div ref={sortable.ref}>
  <span ref={sortable.activatorRef} class="handle">&#x2801;</span>
  Item content
</div>

Keyboard support

Add createKeyboardSensor for accessible drag-and-drop. Space/Enter to pick up and drop, arrow keys to move, Escape to cancel.

import { createPointerSensor, createKeyboardSensor } from "solidjs-dnd";

const sensors = [createPointerSensor(), createKeyboardSensor()];

<DragDropProvider sensors={sensors}>
  {/* ... */}
</DragDropProvider>

ARIA attributes (role, tabindex, aria-grabbed, aria-roledescription, aria-describedby) are applied automatically. A live region announces drag events to screen readers.

Axis locking

Constrain dragging to a single axis.

<DragDropProvider lockAxis="y">
  {/* Only vertical dragging */}
</DragDropProvider>

Touch delay

Distinguish drag from scroll on touch devices.

const sensors = [
  createPointerSensor({ touchDelay: 200 }),
  createKeyboardSensor(),
];

If the pointer moves beyond the activation distance before the delay elapses, the gesture is treated as a scroll, not a drag.

API reference

Provider

| Prop | Type | Default | Description | |------|------|---------|-------------| | collisionDetection | CollisionDetector | closestCenter | Collision algorithm | | onDragStart | DragEventHandler | — | Called when drag starts | | onDragOver | DragEventHandler | — | Called when over target changes | | onDragEnd | DragEventHandler | — | Called when drag ends | | sensors | SensorFactory[] | [createPointerSensor()] | Input sensors | | lockAxis | "x" \| "y" | — | Lock to single axis | | announcements | Announcements | Default messages | Screen reader announcements | | screenReaderInstructions | string | Default text | Instructions for focused items |

Primitives

createDraggable(options) / createSortable(options) / createTreeSortable(options)

All return:

  • ref — Assign to element
  • activatorRef — Assign to drag handle (optional)
  • isDragging() — Is this item being dragged
  • transform() — Current { x, y } translation
  • ariaAttributes() — ARIA attributes object

createSortable also returns:

  • isOver() — Is a dragged item over this target
  • isSettling() — Is FLIP animation playing

createTreeSortable also returns:

  • projectedDepth() — Projected nesting depth during drag
  • isInActiveSubtree() — Is this item a descendant of the dragged item

createDroppable(options)

Returns:

  • ref — Assign to element
  • isOver() — Is a dragged item over this target

Collision detection

| Algorithm | Best for | |-----------|----------| | closestCenter | Flat lists, grids | | rectIntersection | Large drop zones | | closestToPointer | Nested droppables (Kanban) |

Sort strategies

| Strategy | Use case | |----------|----------| | verticalListStrategy | Vertical lists (default) | | horizontalListStrategy | Horizontal lists | | createGridStrategy(options?) | CSS grid layouts |

Utilities

// Flat list reordering
moveArrayItem(array, fromIndex, toIndex)
transferArrayItem(source, dest, fromIndex, toIndex)
indexOfId(array, id)

// Tree operations
getSubtreeIds(items, activeId)
computeProjection(items, activeId, overIndex, deltaX, indentSize)
moveTreeItems(items, activeId, projection)

Running the demo

pnpm install
pnpm demo

License

MIT