KanbanBoard

A drag-and-drop Kanban board with keyboard support. Cards can be dragged between columns and reordered within columns. The card face (what gets dragged) ships a default template but can be fully replaced via renderCard. KanbanCard ships no built-in edit form: the details popup is entirely bring-your-own. Pass a renderCardDetails function to render whatever content you want inside the Modal that opens on click; omit it and the board is drag-only, no popup, no info button.

Installation

npx dafink-ui add kanban

Also installs: Modal

Requires: @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities

Demos

To Do

2
Research

Research competitors

Write spec

Define requirements for v2.

In Progress

2
Engineering

Build prototype

Design review

Review mockups with the team.

Done

1

Kick-off meeting

Align on goals and timeline.

Custom card face

KanbanCard ships a default face (tag, title, description, meta row), but renderCard replaces it entirely — the outer draggable wrapper, click-to-open, and keyboard support all keep working. This one swaps in a compact row: a tag-colored dot, the title, and a chevron that calls onViewDetails to open the (equally custom) details popup.

Backlog

2
Set up CI pipeline
Draft onboarding copy

Active

1
Survey churned users

Shipped

1
Launch referral program

Composition

<KanbanBoard>/* data-driven */
<KanbanColumn>
<KanbanCard />
</KanbanColumn>
</KanbanBoard>

Props

NameTypeDefaultDescription
initialColumnsKanbanColumnData[]-Initial column and card data. Column and card state is managed internally.
onCardUpdate(card: KanbanCardData) => void-Fired after a card is edited and saved in its details popup, with the updated card.
onCardDelete(id: string) => void-Fired after a card is deleted from its details popup, with the id of the deleted card.
renderCardDetails(props: { card, onUpdate, onDelete, onClose }) => ReactNode-Renders the content of the details Modal opened when a card is clicked. There is no built-in form — this is entirely bring-your-own. Omit it to disable the popup entirely: cards become drag-only, with no info button and no click behavior.
renderCard(props: { card, isDragging, onViewDetails }) => ReactNode-Renders the card face shown on the board and dragged. Omit it to use the default template (tag, title, description, points/due-date/subtasks meta row, and an info button when renderCardDetails is set). A custom face can call onViewDetails to open the details popup itself.
classNamestring""Extra classes on the board container.

Accessibility

KanbanBoard uses dnd-kit's KeyboardSensor (sortableKeyboardCoordinates) alongside its pointer sensor, so every card is genuinely keyboard-draggable: KanbanCard's root element is the same one carrying useSortable's attributes/listeners, giving it role="button", tabIndex=0, and an aria-label instructing how to start a drag — there's no separate grip handle and no roving tabindex; cards just sit in normal tab order. When renderCardDetails is provided, viewing details moves to an independently focusable info button (Space/Enter on the card itself stay claimed for dragging), and the details Modal it opens follows the standard focus-trap / Escape-closes / return-focus-to-trigger overlay contract.

KeyFunction
tabMoves focus to the next card (or its info button, if present), following normal document order.
spaceStarts dragging the focused card; press again to drop it at its current position.
enterStarts dragging the focused card; press again to drop it at its current position.
up arrowWhile dragging, moves the card up within or across columns.
down arrowWhile dragging, moves the card down within or across columns.
left arrowWhile dragging, moves the card toward the previous column.
right arrowWhile dragging, moves the card toward the next column.
escapeCancels an in-progress drag, returning the card to its original position.