Ask AI about this page

Introduction

The Kanban component provides a flexible board interface for organizing tasks, workflows, and projects into columns. With extensive customization through slots and a clean API, it's perfect for project management, workflow visualization, and any column-based organization system.

Installation

Use the Sheaf artisan command to install the kanban component:

php artisan sheaf:install kanban

Once installed, you can use <x-ui.kanban />, <x-ui.kanban.column />, <x-ui.kanban.header />, <x-ui.kanban.cards />, and <x-ui.kanban.card /> components in any Blade view.

Basic Usage

Axioms

Foundational statements
6
Parallel Postulate
Through a point not on a line, exactly one parallel line exists
Commutativity of Addition
For all numbers a and b: a + b = b + a
Well-Ordering Principle
Every non-empty set of positive integers has a least element
Axiom of Choice
Product of non-empty sets is non-empty

Lemmas

Helper theorems
5
Gauss's Lemma
Product of primitive polynomials is primitive
Zorn's Lemma
Chain condition implies maximal element exists
Pumping Lemma
Used to prove languages are not regular
Fatou's Lemma
Inequality for limit inferior of integrals
Schur's Lemma
Irreducible representation theory

Proven Theorems

4
Pythagorean Theorem
a² + b² = c² for right triangles
Fundamental Theorem of Calculus
Differentiation and integration are inverse operations
Fermat's Last Theorem
No integer solutions for x^n + y^n = z^n when n > 2
Gödel's Incompleteness
Formal systems contain unprovable truths
<x-ui.kanban>
    <x-ui.kanban.column>
        <x-ui.kanban.header :count="6">
            <x-ui.heading>Axioms</x-ui.heading>
            <x-ui.text>Foundational statements</x-ui.text>
        </x-ui.kanban.header>
        
        <x-ui.kanban.cards>
            <x-ui.kanban.card :id="1">
                <x-ui.text class="font-semibold">
                    Parallel Postulate
                </x-ui.text>
                <x-ui.text class="opacity-60 text-xs">
                    Through a point not on a line, exactly one parallel line exists
                </x-ui.text>
            </x-ui.kanban.card>
            <!-- More cards... -->
        </x-ui.kanban.cards>
    </x-ui.kanban.column>
    
    <x-ui.kanban.column>
        <x-ui.kanban.header :count="5">
            <x-ui.heading>Lemmas</x-ui.heading>
            <x-ui.text>Helper theorems</x-ui.text>
        </x-ui.kanban.header>
        
        <x-ui.kanban.cards>
            <!-- Cards... -->
        </x-ui.kanban.cards>
    </x-ui.kanban.column>
    
    <x-ui.kanban.column>
        <x-ui.kanban.header>
            <x-ui.heading>Proven Theorems</x-ui.heading>
        </x-ui.kanban.header>
        
        <x-ui.kanban.cards>
            <x-slot:empty>
                <p class="text-sm text-neutral-500">No proven theorems yet</p>
            </x-slot:empty>
        </x-ui.kanban.cards>
    </x-ui.kanban.column>
</x-ui.kanban>

Column Width

Control column width using CSS custom properties:

Conjectures

4
Collatz Conjecture
Proposed 1937
Twin Prime Conjecture
Infinitely many twin primes exist
Goldbach's Conjecture
Every even integer > 2 is sum of two primes
Riemann Hypothesis
Zeros of zeta function

Proof Techniques

5
Proof by Contradiction
Assume negation leads to impossibility
Mathematical Induction
Base case + inductive step
Direct Proof
Logical chain from hypothesis to conclusion
Proof by Contrapositive
Prove ¬Q → ¬P instead of P → Q
Proof by Cases
Exhaust all possibilities
<!-- Narrow columns (18rem) -->
<x-ui.kanban class="[--column-width:18rem]">
    <!-- Columns will be 18rem wide -->
</x-ui.kanban>

<!-- Wide columns (24rem) -->
<x-ui.kanban class="[--column-width:28rem]">
    <!-- Columns will be 28rem wide -->
</x-ui.kanban>

<!-- (20rem is the default) -->

Card Variations

Card with Top and Bottom Slots

Add metadata above and below the main content, (this helps organize things out more than just optional):

Complexity Classes

5
Polynomial time
P vs NP Problem
Can every problem whose solution can be verified quickly also be solved quickly?
Stephen Cook (1971)
Millennium Prize
Nondeterministic polynomial
Traveling Salesman
Find shortest route visiting all cities exactly once
Classic NP-Complete
Graph Theory
<x-ui.kanban.card>
    <x-slot:top>
        <!-- Content above main card content -->
        <div class="flex items-center justify-between mb-2">
            <x-ui.badge color="green" size="sm">P</x-ui.badge>
            <x-ui.text size="xs">Polynomial time</x-ui.text>
        </div>
    </x-slot:top>
    
    <!-- Main card content -->
    <div>
        <x-ui.text class="font-semibold">P vs NP Problem</x-ui.text>
        <x-ui.text size="sm">Description here</x-ui.text>
    </div>
    
    <x-slot:bottom>
        <!-- Content below main card content -->
        <div class="flex items-center justify-between mt-3">
            <x-ui.text size="xs">Stephen Cook (1971)</x-ui.text>
            <x-ui.text size="xs">Millennium Prize</x-ui.text>
        </div>
    </x-slot:bottom>
</x-ui.kanban.card>

Card Size Variants

Adjust card padding with size variants, use them as your condesing more contents :

Extra Small

3

Axiom of Extensionality

Axiom of Pairing

Axiom of Union

Small

3

Identity Element

a + 0 = a

Inverse Element

a + (-a) = 0

Associativity

(a+b)+c = a+(b+c)

Medium (Default)

3

Cauchy Sequence

For all ε > 0, there exists N such that |aₙ - aₘ| < ε for n,m > N

Limit Definition

lim f(x) = L if for all ε > 0, exists δ > 0

Continuity

f continuous at c if lim[x→c] f(x) = f(c)

<!-- Extra small cards -->
<x-ui.kanban.column size="xs">
    <x-ui.kanban.header>
        <x-ui.heading>Tasks</x-ui.heading>
    </x-ui.kanban.header>
    
    <x-ui.kanban.cards>
        <x-ui.kanban.card>
            <h4 class="text-xs">Compact card</h4>
        </x-ui.kanban.card>
    </x-ui.kanban.cards>
</x-ui.kanban.column>

<!-- Small cards -->
<x-ui.kanban.column size="sm">
    <x-ui.kanban.header>
        <x-ui.heading>Tasks</x-ui.heading>
    </x-ui.kanban.header>
    
    <x-ui.kanban.cards>
        <x-ui.kanban.card>
            <h4 class="text-sm">Small card</h4>
        </x-ui.kanban.card>
    </x-ui.kanban.cards>
</x-ui.kanban.column>

<!-- Medium cards (default) -->
<x-ui.kanban.column>
    <x-ui.kanban.header>
        <x-ui.heading>Tasks</x-ui.heading>
    </x-ui.kanban.header>
    
    <x-ui.kanban.cards>
        <x-ui.kanban.card>
            <h4>Medium card</h4>
        </x-ui.kanban.card>
    </x-ui.kanban.cards>
</x-ui.kanban.column>

Empty States

Provide custom empty states for columns with no cards:

Unsolved Problems

0

No problems

All problems have been solved!
    
<x-ui.kanban.column>
    <x-ui.kanban.header :count="$count">
        <x-ui.heading>Unsolved Problems</x-ui.heading>
    </x-ui.kanban.header>
    @if($empty)
        <x-ui.empty>
            <x-ui.empty.media 
                class="flex items-center justify-center w-12 h-12 rounded-full bg-neutral-100 dark:bg-neutral-800"
            >
                <x-ui.icon name="x-mark" />
            </x-ui.empty.media>
            <x-ui.empty.contents>
                <x-ui.heading>No problems</x-ui.heading>
                <x-ui.text class="opacity-70">
                    All problems have been solved!
                </x-ui.text>
            </x-ui.empty.contents>
        </x-ui.empty>
    @else
        <x-ui.kanban.cards>
            <!--  -->
        </x-ui.kanban.cards>
    @endif
</x-ui.kanban.column>

Column Footers

Add interactive aligned elements to columns footers:

Open Problems

6

Riemann Hypothesis

Zeros of the Riemann zeta function

Navier-Stokes Existence

Smooth solutions for 3D fluid equations

<x-ui.kanban.column>
    <x-ui.kanban.header>
        <x-ui.heading>Tasks</x-ui.heading>
    </x-ui.kanban.header>
    
    <x-ui.kanban.cards>
        <!-- Cards go here -->
    </x-ui.kanban.cards>
    
     <x-ui.kanban.footer>
        <div class="space-y-2">
            <x-ui.input/>
            <div class="flex gap-2">
                <x-ui.button 
                    color="blue" 
                    variant="outline" 
                    size="sm" 
                    icon="plus"
                >
                    Add Problem
                </x-ui.button>
                <x-ui.button 
                    variant="outline" 
                    size="sm" 
                    icon="arrow-up-tray" 
                    class="ml-auto"
                >
                    Import
                </x-ui.button>
            </div>
        </div>
    </x-ui.kanban.footer>
</x-ui.kanban.column>

Implementation Guide

This guide shows you how to build a fully functional kanban board with drag-and-drop reordering using Livewire. We'll create a mathematical research workflow board that tracks conjectures through review to proven theorems.

See the complete kanban board in action Visit Live Demo

Overview

We'll build a kanban board where:

  • Columns represent workflow stages (Conjectures → Under Review → Proven Theorems)
  • Cards represent mathematical statements with progress tracking
  • Users can drag columns to reorder workflow stages
  • Users can drag cards within columns or between columns
  • All reordering persists to the database

Database Setup

First, create the necessary migrations:

Board Migration:

Schema::create('boards', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

BoardColumn Migration:

Schema::create('board_columns', function (Blueprint $table) {
    $table->id();
    $table->foreignId('board_id')->constrained()->cascadeOnDelete();
    $table->string('title');
    $table->text('description')->nullable();
    $table->unsignedInteger('order')->default(0);
    $table->timestamps();
    
    $table->index(['board_id', 'order']);
});

MathematicalStatement Migration:

Schema::create('mathematical_statements', function (Blueprint $table) {
    $table->id();
    $table->foreignId('column_id')->constrained('board_columns')->cascadeOnDelete();
    $table->string('title');
    $table->text('description');
    $table->string('field'); // number_theory, topology, etc.
    $table->integer('year');
    $table->string('status'); // conjecture, review, proven
    $table->unsignedInteger('progress')->default(0); // 0-100
    $table->unsignedInteger('order')->default(0);
    $table->timestamps();
    
    $table->index(['column_id', 'order']);
});

Model Setup

Define the Eloquent relationships:

Board Model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Board extends Model
{
    protected $fillable = ['name'];
    
    public function columns()
    {
        return $this->hasMany(BoardColumn::class)->orderBy('order');
    }
}

BoardColumn Model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class BoardColumn extends Model
{
    protected $fillable = ['board_id', 'title', 'description', 'order'];
    
    public function board()
    {
        return $this->belongsTo(Board::class);
    }
    
    public function statements()
    {
        return $this->hasMany(MathematicalStatement::class, 'column_id')
            ->orderBy('order');
    }
}

MathematicalStatement Model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class MathematicalStatement extends Model
{
    protected $fillable = [
        'column_id', 'title', 'description', 'field', 
        'year', 'status', 'progress', 'order'
    ];
    
    public function column()
    {
        return $this->belongsTo(BoardColumn::class, 'column_id');
    }
}

Using the Reorderable Trait

The kanban component ships with a powerful App\Livewire\Concerns\Reorderable trait that handles all the complex database logic for reordering items. You don't need to write the SQL yourself—just use the trait's methods.

Key Methods:

  1. reorderWithinScope() - Reorder items within the same container (e.g., cards in the same column)
  2. moveBetweenScopes() - Move items between containers (e.g., card from one column to another)
  3. reorderTransaction() - Wrap reordering in a database transaction for safety

Livewire Component

Create your Livewire component that uses the Reorderable trait:

<?php

namespace App\Livewire;

use App\Livewire\Concerns\Reorderable;
use App\Models\Board;
use App\Models\BoardColumn;
use App\Models\MathematicalStatement;
use Livewire\Component;

class KanbanBoard extends Component
{
    use Reorderable;

    public Board $board;

    public function mount(): void
    {
        $this->loadBoard();
    }

    public function render()
    {
        return view('livewire.kanban-board');
    }

    private function loadBoard(): void
    {
        $this->board = Board::query()
            ->with(['columns', 'columns.statements'])
            ->firstOrFail();
    }

    /**
     * Handle column reordering (drag columns to rearrange workflow)
     */
    public function sortColumns(int $columnId, int $newPosition): void
    {
        $this->reorderTransaction(function () use ($columnId, $newPosition) {
            $column = BoardColumn::findOrFail($columnId);

            // Reorder within the board's columns
            $this->reorderWithinScope(
                model: $column,
                newPosition: $newPosition,
                scope: fn ($q) => $q->where('board_id', $column->board_id)
            );
        });

        $this->loadBoard();
    }

    /**
     * Handle card reordering and moving between columns
     */
    public function sortCards(int $cardId, int $newPosition, int $targetColumnId): void
    {
        $this->reorderTransaction(function () use ($cardId, $newPosition, $targetColumnId) {
            $card = MathematicalStatement::findOrFail($cardId);

            if ($card->column_id === $targetColumnId) {
                // Same column - just reorder
                $this->reorderWithinScope(
                    model: $card,
                    newPosition: $newPosition,
                    scope: fn ($q) => $q->where('column_id', $card->column_id)
                );
            } else {
                // Different column - move between scopes
                $this->moveBetweenScopes(
                    model: $card,
                    fromScope: fn ($q) => $q->where('column_id', $card->column_id),
                    toScope: fn ($q) => $q->where('column_id', $targetColumnId),
                    scopeAttributes: ['column_id' => $targetColumnId],
                    newPosition: $newPosition
                );
            }
        });

        $this->loadBoard();
    }
}

How the Reorderable Trait Works:

  • reorderWithinScope(): When you drag a card from position 2 to position 5 within the same column, the trait automatically shifts all affected cards and updates their order values
  • moveBetweenScopes(): When you drag a card from "Conjectures" to "Under Review", the trait handles removing it from the old column, shifting remaining cards, and inserting it at the new position
  • reorderTransaction(): Wraps everything in a database transaction so if anything fails, no partial updates occur

Blade View Implementation

Now create the view with drag-and-drop enabled:

<div class="min-h-screen py-12 px-4">
    <div class="max-w-5xl mx-auto">
        <div class="text-center space-y-2 mb-8">
            <h1 class="text-3xl font-bold">Mathematical Research Board</h1>
            <p class="text-neutral-600 dark:text-neutral-400">
                Track conjectures through peer review to proven theorems
            </p>
        </div>

        <x-ui.kanban 
            class="max-h-[800px]"
            x-sort="$wire.sortColumns"
            x-sort:config="{forceFallback: true}"
        >
            @foreach ($this->board->columns as $column)
                <x-ui.kanban.column 
                    size="sm"
                    x-sort:item="'{{ $column->id }}'"
                >
                    <x-ui.kanban.header :count="count($column->statements)">
                        <x-ui.heading>{{ $column->title }}</x-ui.heading>
                        <x-ui.text>{{ $column->description }}</x-ui.text>
                    </x-ui.kanban.header>

                    <x-ui.kanban.cards
                        x-data="{
                            handle: (item, position) => {
                                $wire.sortCards(item, position, {{ $column->id }})
                            }
                        }"  
                        x-sort:config="{forceFallback: true}"
                        x-sort="handle" 
                        x-sort:group="board-{{ $board->id }}"
                    >
                        @if($column->statements->isEmpty())
                            <x-slot:empty>
                                <div class="text-center py-8">
                                    <p class="text-neutral-500">No statements yet</p>
                                </div>
                            </x-slot:empty>
                        @else
                            @foreach ($column->statements as $statement)
                                <x-ui.kanban.card 
                                    size="sm"
                                    x-sort:item="{{ $statement->id }}"
                                >
                                    <x-slot:top>
                                        <div class="flex items-center justify-between mb-2">
                                            @php
                                                $fieldColors = [
                                                    'number_theory' => 'yellow',
                                                    'topology' => 'blue',
                                                    'analysis' => 'indigo',
                                                    'graph_theory' => 'pink',
                                                ];
                                                $color = $fieldColors[$statement->field] ?? 'neutral';
                                            @endphp
                                            
                                            <x-ui.badge 
                                                variant="outline" 
                                                color="{{ $color }}" 
                                                size="sm"
                                            >
                                                {{ ucfirst(str_replace('_', ' ', $statement->field)) }}
                                            </x-ui.badge>
                                            
                                            <x-ui.text size="xs" class="opacity-60">
                                                {{ $statement->year }}
                                            </x-ui.text>
                                        </div>
                                    </x-slot:top>

                                    <div>
                                        <x-ui.text class="font-semibold">
                                            {{ $statement->title }}
                                        </x-ui.text>
                                        <x-ui.text size="xs" class="opacity-60 mt-1">
                                            {{ $statement->description }}
                                        </x-ui.text>
                                        
                                        @if($statement->status === 'review')
                                            <div class="mt-2">
                                                <x-ui.progress 
                                                    :value="$statement->progress"
                                                    color="blue"
                                                    size="sm"
                                                    showValue
                                                    label="Verification"
                                                />
                                            </div>
                                        @endif
                                    </div>
                                </x-ui.kanban.card>
                            @endforeach
                        @endif
                    </x-ui.kanban.cards>
                </x-ui.kanban.column>
            @endforeach
        </x-ui.kanban>
    </div>
</div>

Key Attributes Explained:

  • x-sort="$wire.sortColumns" on the board - Enables column drag-and-drop
  • x-sort:item="'{{ $column->id }}'" - Identifies which column is being dragged
  • x-sort="handle" on cards container - Uses custom handler for card sorting
  • x-sort:group="board-{{ $board->id }}" - Allows cards to be dragged between columns
  • x-sort:item="{{ $statement->id }}" - Identifies which card is being dragged

Component Props

ui.kanban

Prop Type Default Description
size string 'md' Board size variant
class string - Additional CSS classes
--column-width CSS var 20rem Width of each column (use arbitrary values like [--column-width:24rem])

ui.kanban.column

Prop Type Default Description
id string null Unique identifier for the column
size string 'md' Size variant (inherited from board or set explicitly)
header slot - Custom header content (replaces default header)
footer slot - Footer content at bottom of column

ui.kanban.header

Prop Type Default Description
count number null Item count badge (displayed in header)

ui.kanban.cards

Prop Type Default Description
empty slot - Custom empty state when no cards present

ui.kanban.card

Prop Type Default Description
id string null Unique identifier for the card
size string 'md' Size variant: 'xs', 'sm', 'md' (inherited from column or set explicitly)
top slot - Content above main card content
bottom slot - Content below main card content
handle slot - Drag handle (shown on hover, positioned top-left)

© SheafUI Copyright 2024-2026. All rights reserved.