Components

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.

Client Only

Installation

pnpm dlx @docyrus/cli add @docyrus/ui-kanban
Required Packages(5 packages)
pnpm add @dnd-kit/sortable @dnd-kit/core @dnd-kit/utilities @radix-ui/react-slot @types/react

Usage

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

ComponentDescription
KanbanRoot provider. Wraps DndContext with collision detection, keyboard navigation, and screen reader announcements.
KanbanBoardContainer for columns. Renders a SortableContext for column reordering.
KanbanColumnA sortable column container. Renders a SortableContext for its items.
KanbanColumnHandleDrag handle for reordering columns. Must be inside KanbanColumn.
KanbanItemA sortable item within a column. Supports cross-column drag.
KanbanItemHandleDrag handle for items. Must be inside KanbanItem.
KanbanItemTitleClickable title zone inside KanbanItem. Prevents drag-and-drop and fires onItemClick.
KanbanOverlayPortal-rendered drag overlay preview. Accepts a render function for custom previews.
KanbanFinalZoneContainer for final drop columns. Only visible during item drag.
KanbanFinalColumnA droppable target inside KanbanFinalZone that triggers onFinalDrop.

API Reference

Kanban

The root component. Manages drag state, collision detection, and value changes.

PropTypeDefaultDescription
valueRecord<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.
finalColumnsUniqueIdentifier[]—IDs of final drop columns (must match KanbanFinalColumn values).
strategySortableContextProps['strategy']verticalListSortingStrategySorting strategy for items within columns.
orientation'horizontal' | 'vertical''horizontal'Board layout direction.
flatCursorbooleanfalseUse cursor-default instead of cursor-grab for all drag handles.
modifiersDndContextProps['modifiers']—dnd-kit modifiers for constraining drag movement.
accessibilityDndContextProps['accessibility']—Custom accessibility announcements (merged with built-in announcements).

KanbanBoard

Container for columns. Must be inside Kanban.

PropTypeDefaultDescription
asChildbooleanfalseRender as child element using Slot.
classNamestring—Additional CSS classes.

KanbanColumn

A sortable column. Must be inside KanbanBoard or KanbanOverlay.

PropTypeDefaultDescription
valueUniqueIdentifier—Unique column identifier. Must match a key in Kanban's value.
asChildbooleanfalseRender as child element using Slot.
asHandlebooleanfalseMake the entire column a drag handle.
disabledbooleanfalseDisable column dragging.
classNamestring—Additional CSS classes.

KanbanColumnHandle

Drag handle for columns. Must be inside KanbanColumn.

PropTypeDefaultDescription
asChildbooleanfalseRender as child element using Slot.
disabledboolean—Override column's disabled state.
classNamestring—Additional CSS classes.

KanbanItem

A sortable item. Must be inside KanbanColumn (within KanbanBoard or KanbanOverlay).

PropTypeDefaultDescription
valueUniqueIdentifier—Unique item identifier. Must match the value returned by getItemValue.
asChildbooleanfalseRender as child element using Slot.
asHandlebooleanfalseMake the entire item a drag handle.
disabledbooleanfalseDisable item dragging.
classNamestring—Additional CSS classes.

KanbanItemHandle

Drag handle for items. Must be inside KanbanItem.

PropTypeDefaultDescription
asChildbooleanfalseRender as child element using Slot.
disabledboolean—Override item's disabled state.
classNamestring—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.

PropTypeDefaultDescription
asChildbooleanfalseRender as child element using Slot.
classNamestring—Additional CSS classes.

KanbanOverlay

Portal-rendered drag preview overlay.

PropTypeDefaultDescription
childrenReactNode | ((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.
containerElement | DocumentFragment | nulldocument.bodyPortal container.

KanbanFinalZone

Container for final drop targets. Shows data-dragging attribute when an item is being dragged.

PropTypeDefaultDescription
asChildbooleanfalseRender as child element using Slot.
classNamestring—Additional CSS classes.

KanbanFinalColumn

A droppable zone for permanently removing items from columns (e.g., archive, complete, delete).

PropTypeDefaultDescription
valueUniqueIdentifier—Unique identifier. Must be listed in Kanban's finalColumns array.
asChildbooleanfalseRender as child element using Slot.
classNamestring—Additional CSS classes.

Data Attributes

AttributeComponentDescription
data-draggingKanbanColumn, KanbanItem, KanbanColumnHandle, KanbanItemHandle, KanbanItemTitle, KanbanFinalZone, KanbanFinalColumnPresent when the element (or its parent) is being dragged.
data-disabledKanbanColumn, KanbanItem, KanbanColumnHandle, KanbanItemHandlePresent when the element is disabled.
data-valueKanbanItemThe item's unique identifier value. Used internally by KanbanItemTitle.
data-overKanbanFinalColumnPresent when a dragged item is hovering over the final column.
data-orientationKanbanBoardCurrent board orientation (horizontal or vertical).
data-slotAllComponent identifier (kanban-board, kanban-column, kanban-item, etc.).

Keyboard Navigation

KeyAction
Space / EnterPick up or drop the focused item/column.
Arrow KeysMove the picked-up item/column in the corresponding direction.
EscapeCancel the current drag operation.

On this page