Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Livewire Status Board Laravel Package

asantibanez/livewire-status-board

A Laravel Livewire package for building interactive status boards and Kanban-style workflows. Display records in customizable columns, drag and drop items between statuses, and persist changes with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

asantibanez/livewire-status-board is a thin base Livewire component that renders a kanban-style board: an array of "statuses," each holding a collection of records, with SortableJS wired up so records can be dragged between columns. Each drop calls a method on your component, and persistence is entirely up to you. There is no database layer, no status model, no migration — it is a rendering and drag-event package.

Minimal path:

composer require asantibanez/livewire-status-board
php artisan vendor:publish --tag=livewire-status-board-config
php artisan vendor:publish --tag=livewire-status-board-views   # only if you need to customise

Then subclass the base component and implement statuses():

use Asantibanez\LivewireStatusBoard\LivewireStatusBoard;

class TaskBoard extends LivewireStatusBoard
{
    public function statuses(): array
    {
        return [
            ['id' => 'todo',  'name' => 'To Do',  'model' => Task::class,
             'records' => Task::where('status', 'todo')->get()],
            ['id' => 'doing', 'name' => 'Doing',  'model' => Task::class,
             'records' => Task::where('status', 'doing')->get()],
            ['id' => 'done',  'name' => 'Done',   'model' => Task::class,
             'records' => Task::where('status', 'done')->get()],
        ];
    }

    public function onStatusChanged($statusId, $recordId)
    {
        Task::findOrFail($recordId)->update(['status' => $statusId]);
    }
}

Drop it in a view with @livewire('task-board').

Where to look first, in this order:

  1. The base class source (src/LivewireStatusBoard.php) — it is small (a few hundred lines at most). Read it end-to-end before adopting; that is faster than reading the README twice.
  2. The published config — it controls which Blade view is used, i.e. your primary extension point.
  3. The published view — it shows the exact data contract ($statuses, per-status id/name/model/records) and how SortableJS is initialised. This is the file you will actually edit.

Check composer.json for the Livewire constraint before committing. The current release line is 2.2.0, whose only changelog entry is "Bumped up dependencies" — i.e. no code changes to the component, but the illuminate/* / livewire/* version floors may have moved. Run composer why livewire/livewire after install and confirm it resolves against your Livewire major version (v2 vs v3 differ in event dispatch and asset directives). If it doesn't, the base class is small enough to vendor into app/ and adapt — that is a realistic fallback, not a crisis.

The first use case worth shipping: a per-project or per-user task board where the status column is a plain string or enum cast. Do not try to make this a general-purpose workflow engine on day one.

Implementation Patterns

Read-only dashboards. Omit the model key entirely (or leave onStatusChanged() as a no-op) and the board becomes a status-grouped overview — useful for support queues, order pipelines, or deployment dashboards. Drag will still fire the event; just don't act on it, or drop the model key, which is what gates draggability in most versions.

Scoped boards. Pass context through mount() and keep it in public properties, then filter inside statuses():

public function mount(Project $project) { $this->projectId = $project->id; }

public function statuses(): array {
    $tasks = Task::where('project_id', $this->projectId)->with('assignee')->get()->groupBy('status');
    return collect(TaskStatus::cases())->map(fn ($s) => [
        'id' => $s->value, 'name' => $s->label(), 'model' => Task::class,
        'records' => $tasks->get($s->value, collect()),
    ])->all();
}

Deriving statuses from an enum keeps the board and the database in lockstep — one source of truth, no drift when you add a status.

Persist, then let the re-render do the work. Since statuses() is re-evaluated on every render, you don't need to manually refresh anything after onStatusChanged(): save the record, and the board repaints with the new groupings. This is the package's whole magic trick, and it's why statuses() must always query the database rather than returning cached state.

Custom cards. Publish the view and edit the card markup. You get the record plus the status context, so you can render avatars, due-date badges, wire:click="$emit('openTask', {{ $record->id }})", or a dropdown menu. Keep the outer wrapper element and its wire:key intact — SortableJS identifies columns by the data attributes the package sets, and removing them silently kills drag-and-drop.

Combine with a create form. Put a small wire:submit form above the board in the same parent view; after creating a task with status = 'todo', the board's next render picks it up. If the form is a separate component, use $refresh or an event listener to force the board to re-render.

Testing. It's a normal Livewire component, so Livewire::test(TaskBoard::class)->call('onStatusChanged', 'done', $task->id)->assertHasNoErrors() plus a database assertion covers the only logic you wrote. There is nothing to mock.

Gotchas and Tips

  • statuses() runs on every render, including every drag. That is N queries per drop. Eager-load relationships, add an index on the status column, and consider ->limit() per column for large tables. This is the most likely production surprise.
  • $recordId arrives as a string from the browser. findOrFail() handles it, but strict comparisons ($record->id === $recordId) will fail silently. Cast explicitly.
  • onStatusChanged is a public, browser-callable method. Anyone can POST an arbitrary $statusId / $recordId pair. Authorise inside the method — $this->authorize('update', $task) and validate that $statusId is a legal transition — rather than trusting the payload.
  • Status ids are used as DOM identifiers and array keys. Use stable, unique, slug-safe strings. Integer-like IDs get recast by PHP when used as array keys, which produces confusing mismatches between the JS payload and your statuses() array.
  • Mixed models break ID routing. The per-status model key implies polymorphism, but only the record ID crosses the wire. If two models share IDs, prefix them ("task-42") or keep one model per board.
  • Within-column reordering is not persisted. The package's value proposition is cross-column movement; sort order inside a column is cosmetic unless you add your own position column and handle it. Don't promise users drag-to-reorder without building it.
  • Published views freeze. Once published, updates to the package's Blade file won't reach you. Diff against vendor/ after upgrades, or keep your overrides as small as possible.
  • Tailwind and CDN assumptions. The default markup targets Tailwind classes and typically pulls SortableJS from a CDN. In a CSP-restricted or offline environment, publish the view and self-host the script, or the board renders inert with no error.
  • Don't nest it in another sortable library (Vue Draggable, Alpine Sort, other kanban widgets). Two drag managers on the same DOM node produce ghost drags and dropped events that are painful to debug.
  • Debugging tip: open the network tab and inspect the livewire/message request payload after a failed drop. It shows exactly which method was called with which parameters — that answers 90% of "the drag does nothing" reports (usually: model missing, or a view override that dropped the wrapper element).
  • Upgrading to 2.2.0 is low-risk but re-lock deliberately. There are no code changes, only dependency bumps, so the practical risk is Composer resolving a newer Livewire/Illuminate than you tested against. Pin the version you validated ("asantibanez/livewire-status-board": "2.2.0"), run your board's Livewire test after composer update, and re-check the published view still matches vendor/ if you overrode it.
  • Maintenance posture: 339 stars, a single maintainer, a 2.2.0 release whose entire scope is dependency maintenance, near-zero dependents. Treat it as readable starter code rather than critical infrastructure: pin the version, keep your subclass thin, and be prepared to fork the base class into app/Livewire/ if Livewire's next major breaks it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo