Data display

Kanban board

A Kanban board that a keyboard can actually use.

Client componentSource

Import

import { KanbanBoard } from '@the_viveksingh/vivek-ui'

A sprint board you can drive from the keyboard

HTML5 drag-and-drop has no keyboard equivalent at all, which is why nearly every Kanban board is mouse-only. This has two complete input paths: dragging, and a pick-up / move / drop model announced through a live region.

Drag with a mouse, or Tab to a card and press Enter to pick it up — then arrow keys to move, Enter to drop, Escape to cancel. Every move is announced to a screen reader.

Backlog2
  • Audit the colour paletteP3

    Check every token pair for contrast.

    Press Enter or Space to pick this card up.
  • Write the migration noteP2
    Press Enter or Space to pick this card up.
In progress1 / 2
  • Ship the Kanban boardP1

    Keyboard path is the hard part.

    Press Enter or Space to pick this card up.
In review1
  • FileTree keyboard modelP1
    Press Enter or Space to pick this card up.
Done1
  • VirtualList
    Press Enter or Space to pick this card up.
const [columns, setColumns] = useState(initial)

<KanbanBoard
  columns={columns}
  label="Sprint board"
  // Nothing is mutated for you - the board reports the intended move.
  onMove={({ cardId, fromColumnId, toColumnId, toIndex }) =>
    setColumns((current) => applyMove(current, cardId, fromColumnId, toColumnId, toIndex))
  }
/>

// Columns carry an optional work-in-progress limit, which blocks drops when reached:
const initial = [
  { id: 'backlog', title: 'Backlog', cards: [...] },
  { id: 'doing', title: 'In progress', cards: [...], limit: 2 },
  { id: 'done', title: 'Done', cards: [] },
]

Props

Generated from the package's own type declarations, so this table cannot drift from the code.

Props for KanbanBoard
PropTypeDefaultDescription
columns requiredreadonly KanbanColumn[]——
label requiredstring—Required. A board with no accessible name is one more unlabelled region.
onMove(move: KanbanMove) => void—Nothing is moved for you — apply this to your own state.
renderCard(card: KanbanCard, column: KanbanColumn) => ReactNode—Rendered instead of the default card body.

Every remaining prop is spread onto the root element, so all standard HTML and ARIA attributes work. className and style are merged with the library's own, never replaced, and the ref forwards to the root DOM node.

Rendering