Kanban
A drag-and-drop kanban board with sortable columns and items, column reordering, overlay previews, and final drop zones for archiving or completing tasks.
Installation
pnpm dlx @docyrus/cli add @docyrus/ui-kanbanpnpm add @dnd-kit/sortable @dnd-kit/core @dnd-kit/utilities @radix-ui/react-slot @types/reactUsage
import {
Kanban,
KanbanBoard,
KanbanColumn,
KanbanColumnHandle,
KanbanItem,
KanbanItemHandle,
KanbanItemTitle,
KanbanOverlay,
KanbanFinalZone,
KanbanFinalColumn
} from '@docyrus/ui/components/kanban';
const [columns, setColumns] = useState({
todo: [{ id: '1', title: 'Task 1' }, { id: '2', title: 'Task 2' }],
done: [{ id: '3', title: 'Task 3' }]
});
<Kanban
value={columns}
onValueChange={setColumns}
getItemValue={(item) => item.id}
>
<KanbanBoard>
{Object.entries(columns).map(([columnId, items]) => (
<KanbanColumn key={columnId} value={columnId}>
<KanbanColumnHandle>⋮</KanbanColumnHandle>
{items.map((item) => (
<KanbanItem key={item.id} value={item.id} asHandle>
{item.title}
</KanbanItem>
))}
</KanbanColumn>
))}
</KanbanBoard>
<KanbanOverlay />
</Kanban>With Final Drop Zones
Use finalColumns on Kanban and KanbanFinalZone + KanbanFinalColumn to create drop targets that remove items from columns (e.g., archive or complete).
<Kanban
value={columns}
onValueChange={setColumns}
getItemValue={(item) => item.id}
finalColumns={['done', 'archived']}
onFinalDrop={(item, finalColumnId) => {
console.log(`${item.id} dropped to ${finalColumnId}`);
}}
>
<KanbanBoard>{/* ... columns ... */}</KanbanBoard>
<KanbanFinalZone>
<KanbanFinalColumn value="done">Done</KanbanFinalColumn>
<KanbanFinalColumn value="archived">Archive</KanbanFinalColumn>
</KanbanFinalZone>
<KanbanOverlay />
</Kanban>With Custom Overlay
The KanbanOverlay children can be a render function that receives the active item's value and variant.
<KanbanOverlay>
{({ value, variant }) => {
if (variant === 'column') {
return <div className="h-full rounded-lg border bg-primary/5" />;
}
const task = taskMap.get(String(value));
return (
<div className="rounded-lg border bg-card p-3 shadow-lg">
{task?.title}
</div>
);
}}
</KanbanOverlay>Item Detail
Use onItemClick and KanbanItemTitle to open item details. KanbanItemTitle creates a clickable title zone that prevents drag-and-drop and triggers the callback instead.
<Kanban
value={columns}
onValueChange={setColumns}
getItemValue={(item) => item.id}
onItemClick={(value) => {
console.log('Open item detail:', value);
}}
>
<KanbanBoard>
{Object.entries(columns).map(([columnId, items]) => (
<KanbanColumn key={columnId} value={columnId}>
<KanbanColumnHandle>⋮</KanbanColumnHandle>
{items.map((item) => (
<KanbanItem key={item.id} value={item.id} asHandle>
<KanbanItemTitle className="font-semibold truncate">
{item.title}
</KanbanItemTitle>
<p className="text-sm text-muted-foreground">{item.description}</p>
</KanbanItem>
))}
</KanbanColumn>
))}
</KanbanBoard>
<KanbanOverlay />
</Kanban>Vertical Layout
Set orientation="vertical" for a top-to-bottom column layout.
<Kanban
value={columns}
onValueChange={setColumns}
getItemValue={(item) => item.id}
orientation="vertical"
>
{/* ... */}
</Kanban>Components
| Component | Description |
|---|---|
Kanban | Root provider. Wraps DndContext with collision detection, keyboard navigation, and screen reader announcements. |
KanbanBoard | Container for columns. Renders a SortableContext for column reordering. |
KanbanColumn | A sortable column container. Renders a SortableContext for its items. |
KanbanColumnHandle | Drag handle for reordering columns. Must be inside KanbanColumn. |
KanbanItem | A sortable item within a column. Supports cross-column drag. |
KanbanItemHandle | Drag handle for items. Must be inside KanbanItem. |
KanbanItemTitle | Clickable title zone inside KanbanItem. Prevents drag-and-drop and fires onItemClick. |
KanbanOverlay | Portal-rendered drag overlay preview. Accepts a render function for custom previews. |
KanbanFinalZone | Container for final drop columns. Only visible during item drag. |
KanbanFinalColumn | A droppable target inside KanbanFinalZone that triggers onFinalDrop. |
API Reference
Kanban
The root component. Manages drag state, collision detection, and value changes.
| Prop | Type | Default | Description |
|---|---|---|---|
value | Record<UniqueIdentifier, T[]> | — | Column data. Keys are column IDs, values are item arrays. |
onValueChange | (columns: Record<UniqueIdentifier, T[]>) => void | — | Called when items are moved between or within columns. |
getItemValue | (item: T) => UniqueIdentifier | — | Extracts a unique ID from each item. Required when T is an object. |
onMove | (event: DragEndEvent & { activeIndex: number; overIndex: number }) => void | — | Called on reorder instead of onValueChange when provided. Useful for server-side persistence. |
onFinalDrop | (item: T, finalColumnId: UniqueIdentifier) => void | — | Called when an item is dropped onto a KanbanFinalColumn. |
onItemClick | (value: UniqueIdentifier) => void | — | Called when a KanbanItemTitle is clicked. Receives the item's value. |
finalColumns | UniqueIdentifier[] | — | IDs of final drop columns (must match KanbanFinalColumn values). |
strategy | SortableContextProps['strategy'] | verticalListSortingStrategy | Sorting strategy for items within columns. |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Board layout direction. |
flatCursor | boolean | false | Use cursor-default instead of cursor-grab for all drag handles. |
modifiers | DndContextProps['modifiers'] | — | dnd-kit modifiers for constraining drag movement. |
accessibility | DndContextProps['accessibility'] | — | Custom accessibility announcements (merged with built-in announcements). |
KanbanBoard
Container for columns. Must be inside Kanban.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child element using Slot. |
className | string | — | Additional CSS classes. |
KanbanColumn
A sortable column. Must be inside KanbanBoard or KanbanOverlay.
| Prop | Type | Default | Description |
|---|---|---|---|
value | UniqueIdentifier | — | Unique column identifier. Must match a key in Kanban's value. |
asChild | boolean | false | Render as child element using Slot. |
asHandle | boolean | false | Make the entire column a drag handle. |
disabled | boolean | false | Disable column dragging. |
className | string | — | Additional CSS classes. |
KanbanColumnHandle
Drag handle for columns. Must be inside KanbanColumn.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child element using Slot. |
disabled | boolean | — | Override column's disabled state. |
className | string | — | Additional CSS classes. |
KanbanItem
A sortable item. Must be inside KanbanColumn (within KanbanBoard or KanbanOverlay).
| Prop | Type | Default | Description |
|---|---|---|---|
value | UniqueIdentifier | — | Unique item identifier. Must match the value returned by getItemValue. |
asChild | boolean | false | Render as child element using Slot. |
asHandle | boolean | false | Make the entire item a drag handle. |
disabled | boolean | false | Disable item dragging. |
className | string | — | Additional CSS classes. |
KanbanItemHandle
Drag handle for items. Must be inside KanbanItem.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child element using Slot. |
disabled | boolean | — | Override item's disabled state. |
className | string | — | Additional CSS classes. |
KanbanItemTitle
Clickable title zone inside KanbanItem. Intercepts pointer events to prevent drag-and-drop activation, and fires the root onItemClick callback on click. Renders a <button> by default.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child element using Slot. |
className | string | — | Additional CSS classes. |
KanbanOverlay
Portal-rendered drag preview overlay.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | ((params: { value: UniqueIdentifier; variant: 'column' | 'item' }) => ReactNode) | — | Static content or render function for custom preview. Receives the active item's value and whether it's a column or item. |
container | Element | DocumentFragment | null | document.body | Portal container. |
KanbanFinalZone
Container for final drop targets. Shows data-dragging attribute when an item is being dragged.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child element using Slot. |
className | string | — | Additional CSS classes. |
KanbanFinalColumn
A droppable zone for permanently removing items from columns (e.g., archive, complete, delete).
| Prop | Type | Default | Description |
|---|---|---|---|
value | UniqueIdentifier | — | Unique identifier. Must be listed in Kanban's finalColumns array. |
asChild | boolean | false | Render as child element using Slot. |
className | string | — | Additional CSS classes. |
Data Attributes
| Attribute | Component | Description |
|---|---|---|
data-dragging | KanbanColumn, KanbanItem, KanbanColumnHandle, KanbanItemHandle, KanbanItemTitle, KanbanFinalZone, KanbanFinalColumn | Present when the element (or its parent) is being dragged. |
data-disabled | KanbanColumn, KanbanItem, KanbanColumnHandle, KanbanItemHandle | Present when the element is disabled. |
data-value | KanbanItem | The item's unique identifier value. Used internally by KanbanItemTitle. |
data-over | KanbanFinalColumn | Present when a dragged item is hovering over the final column. |
data-orientation | KanbanBoard | Current board orientation (horizontal or vertical). |
data-slot | All | Component identifier (kanban-board, kanban-column, kanban-item, etc.). |
Keyboard Navigation
| Key | Action |
|---|---|
Space / Enter | Pick up or drop the focused item/column. |
Arrow Keys | Move the picked-up item/column in the corresponding direction. |
Escape | Cancel the current drag operation. |
Jsonata Editor
A three-pane workbench for writing and evaluating JSONata expressions — a JSON input pane, an expression editor with IntelliSense, and a live result pane.
Log Activity Form
A presentational multi-tab "log activity" composer — comment, email, call, meeting, task, and status — with every tab rendered inline, wired to Docyrus via the useDocyrusLogActivity hook.