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

wire-sortable

v1.0.5

Published

Drag and drop sorting for Laravel Livewire, powered by SortableJS

Readme


Introduction

Livewire doesn't ship with built-in drag and drop support. The official livewire-sortable package relies on Shopify Draggable, which is heavier, less actively maintained, and has known issues with wire:navigate and Livewire 3+ lifecycle events.

wire-sortable is a lightweight alternative built on SortableJS, the most widely used drag and drop library in the JavaScript ecosystem. It integrates seamlessly with Livewire using simple HTML attributes: no PHP package required, no configuration needed. Just import and go.

| | wire-sortable | livewire-sortable (official) | |---|---|---| | Engine | SortableJS (actively maintained) | Shopify Draggable (less active) | | Multi-container | wire:sortable.group + wire:sortable.container | wire:sortable-group (separate API) | | Custom options | wire:sortable.options | Limited | | Swap mode | Built-in | No | | Bundle size | ~36 KB min | ~60 KB min | | Livewire 3+ | Yes | Yes | | wire:navigate | Yes | Partial |

👉 See it in action

Installation

npm install wire-sortable

Import in your JavaScript entry file:

// resources/js/app.js
import 'wire-sortable';

That's it. No service providers, no config files.

Quick Start

1. Simple list sorting

// app/Livewire/TodoList.php
class TodoList extends Component
{
    public $todos;

    public function mount()
    {
        $this->todos = Todo::orderBy('order')->get();
    }

    public function updateOrder($items)
    {
        foreach ($items as $item) {
            Todo::find($item['value'])->update(['order' => $item['order']]);
        }
    }
}
<ul wire:sortable="updateOrder">
    @foreach($todos as $todo)
        <li wire:sortable.item="{{ $todo->id }}" wire:key="todo-{{ $todo->id }}">
            {{ $todo->title }}
        </li>
    @endforeach
</ul>

2. With drag handles

Only the handle element triggers dragging:

<ul wire:sortable="updateOrder">
    @foreach($items as $item)
        <li wire:sortable.item="{{ $item->id }}" wire:key="item-{{ $item->id }}"
            class="flex items-center gap-3 p-3 bg-white rounded-lg shadow">
            <span wire:sortable.handle class="cursor-grab text-gray-400 hover:text-gray-600">
                <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"><path d="M7 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm6 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zM7 8a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm6 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zM7 14a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm6 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/></svg>
            </span>
            <span>{{ $item->name }}</span>
        </li>
    @endforeach
</ul>

3. Kanban board (multi-container)

Drag items between columns:

public function updateTaskOrder($items, $toStatus, $fromStatus)
{
    foreach ($items as $item) {
        Task::find($item['value'])->update([
            'order' => $item['order'],
            'status' => $toStatus,
        ]);
    }

    $this->tasks = Task::orderBy('order')->get()->groupBy('status');
}
<div class="grid grid-cols-3 gap-4">
    @foreach(['backlog', 'in_progress', 'done'] as $status)
        <div class="bg-gray-100 rounded-lg p-4">
            <h3 class="font-bold mb-3">{{ ucfirst($status) }}</h3>

            <div wire:sortable="updateTaskOrder"
                 wire:sortable.group="board"
                 wire:sortable.container="{{ $status }}"
                 class="space-y-2 min-h-[100px]">

                @foreach($tasks[$status] ?? [] as $task)
                    <div wire:sortable.item="{{ $task->id }}" wire:key="task-{{ $task->id }}"
                         class="p-3 bg-white rounded shadow">
                        {{ $task->title }}
                    </div>
                @endforeach
            </div>
        </div>
    @endforeach
</div>

4. Image gallery reordering

<div wire:sortable="reorderImages"
     wire:sortable.options='{"animation": 200, "ghostClass": "opacity-30"}'
     class="grid grid-cols-4 gap-2">
    @foreach($images as $image)
        <div wire:sortable.item="{{ $image->id }}" wire:key="img-{{ $image->id }}">
            <img src="{{ $image->url }}" class="rounded-lg" />
        </div>
    @endforeach
</div>

Directives

| Directive | Description | |---|---| | wire:sortable="method" | Livewire method called on sort. Receives ($items, $toContainer, $fromContainer) | | wire:sortable.item="id" | Marks an element as sortable. The value is passed in the $items array | | wire:sortable.handle | Only this element triggers dragging (optional) | | wire:sortable.group="name" | Groups containers: items can only move between containers with the same group | | wire:sortable.container="id" | Identifies the container, passed as $toContainer / $fromContainer | | wire:sortable.options='JSON' | Custom SortableJS options as JSON |

The $items array

Your Livewire method receives an array of items in their new order:

public function updateOrder($items, $toContainer = null, $fromContainer = null)
{
    // $items = [
    //     ['order' => 1, 'value' => '42'],
    //     ['order' => 2, 'value' => '17'],
    //     ['order' => 3, 'value' => '8'],
    // ]
}

Custom options

Pass any SortableJS option via wire:sortable.options:

<div wire:sortable="updateOrder"
     wire:sortable.options='{
         "animation": 300,
         "delay": 100,
         "delayOnTouchOnly": true,
         "ghostClass": "opacity-50",
         "chosenClass": "ring-2 ring-blue-500",
         "dragClass": "shadow-2xl"
     }'>

Default options

| Option | Default | |---|---| | animation | 150 | | ghostClass | sortable-ghost | | chosenClass | sortable-chosen |

Styling

/* Placeholder shown where the item will be dropped */
.sortable-ghost {
    opacity: 0.4;
    background: #f0f9ff;
}

/* The element being dragged */
.sortable-chosen {
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}

Or use Tailwind classes directly via options:

wire:sortable.options='{"ghostClass": "opacity-30 bg-blue-50", "chosenClass": "shadow-xl"}'

Programmatic re-initialization

When dynamically adding sortable containers after the initial render:

document.dispatchEvent(new Event('reinit-sortable'));

Advanced: Access SortableJS directly

import { Sortable, initSortable } from 'wire-sortable';

// Use Sortable directly for custom implementations
const el = document.getElementById('my-list');
Sortable.create(el, { /* options */ });

// Manually re-initialize all wire:sortable elements
initSortable();

Requirements

  • Livewire 3+
  • A JavaScript bundler (Vite, Webpack, esbuild, etc.)

CDN Usage

For projects without a build step:

<script src="https://unpkg.com/wire-sortable@latest/dist/wire-sortable.min.js"></script>

Author

Created by Edu Lazaro

License

wire-sortable is open-sourced software licensed under the MIT license.