Docyrus

Kanban

Horizontally scrollable kanban board with generic items, position-preserving drag, column reorder, final drop zones, and rich default cards.

iOSAndroid
Preview Kanban on your device

Scan with Expo Go

Download Expo Go, then scan the QR code to preview native components.

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-kanban
Required Packages(2 packages)
pnpm add react-native-gesture-handler react-native-reanimated

Usage

import { DocyKanban, type DocyKanbanColumn, type DocyKanbanItem } from '@/components/docyrus-native/kanban';

const columns: DocyKanbanColumn[] = [
  { id: 'todo', title: 'To Do', color: '#6366f1' },
  { id: 'progress', title: 'In Progress', color: '#f59e0b' },
  { id: 'review', title: 'Review', color: '#8b5cf6' },
];

const [items, setItems] = useState<DocyKanbanItem[]>([
  { id: 1, title: 'Setup CI', columnId: 'todo', priority: 'high', tag: 'DevOps' },
  { id: 2, title: 'Auth flow', columnId: 'progress', priority: 'medium', assignee: 'John' },
]);

<DocyKanban
  columns={columns}
  items={items}
  draggable
  showDoneSection
  onItemsChange={setItems}
  onCardPress={(item) => console.log(item.title)}
  onAddCard={(columnId) => console.log('add to', columnId)} />

DocyKanban (also exported as Kanban) is a monolithic adaptation of the web compound primitive (Kanban, KanbanBoard, KanbanColumn, KanbanColumnHandle, KanbanItem, KanbanOverlay, KanbanFinalZone, KanbanFinalColumn, …). On React Native the compound pieces would only re-wrap the same gesture + measurement plumbing, so the web capabilities are exposed as props instead: the board is KanbanBoard, each column header is the KanbanColumnHandle, every card is a KanbanItem, the floating copy that follows the finger is the KanbanOverlay, and final zones are KanbanFinalColumns.

Drag and drop

With draggable, long-press a card for 200 ms (the web touch-sensor delay) and drag it:

  • Reorder inside a column. The other cards slide out of the way as you drag.
  • Move to another column. The card lands exactly at the index shown by the gap.
  • Drop on a final zone (Done / Archive / custom) to mark it final.

The board scrolls horizontally when you drag near its left or right edge. Because a card only activates after the long-press, a normal swipe still scrolls the board.

With columnDraggable (defaults to draggable), long-press a column header and drag it horizontally to reorder the columns. The other columns animate out of the way.

Moves are applied to the board optimistically. The board resyncs whenever items, value, columns or columnOrder changes.

ChangeCallbacks
Reorder inside a columnonMove(event) if set, otherwise onValueChange(next) (web semantics) · onMoveCard · onItemsChange
Move to another columnonValueChange(next) + onMove(event) · onMoveCard · onItemsChange
Drop on a final zoneonValueChange(next) (without the item) · onFinalDrop(item, finalColumnId) · onMoveCard · onItemsChange
Reorder columnsonColumnOrderChange(ids) · onMove(event) if set, otherwise onValueChange(next) (keys in the new order)

Item sources

  • Flat mode (items): items are grouped with getItemColumnId (default item.columnId). With the default accessor, a moved item is returned with its columnId updated (and done / archived set for the presets). So onItemsChange={setItems} keeps both the column AND the position. With a custom accessor, the item is returned untouched: use onValueChange or onMoveCard to persist the move.
  • Grouped mode (value): the web Record<columnId, T[]> shape. It is authoritative for both membership and order. columns is optional here; without it, the keys become the columns.

Generic items

Any record type works. Pass getItemValue (the web name) for the id and a renderCard for the content. This is the mode a Docyrus kanban hook feeds with raw records and DocyrusKanbanColumnMeta columns:

type Deal = { id: string; name: string; amount: number };

<DocyKanban<Deal>
  columns={[
    { id: 'lead', label: 'Lead', icon: 'fal user-plus', color: '#0ea5e9' },
    { id: 'won', label: 'Won', color: '#22c55e', isFinal: true },
  ]}
  value={deals}
  onValueChange={next => setDeals(prev => ({ ...prev, ...next }))}
  getItemValue={deal => deal.id}
  draggable
  onFinalDrop={(deal, columnId) => closeDeal(deal.id, columnId)}
  onItemClick={id => openDeal(id)}
  renderCard={(deal, index, { isOverlay }) => <DealCard deal={deal} lifted={isOverlay} />} />

Final zones

Final zones are drop targets rendered below the board. Tap a zone to expand the items it holds. They come from three sources, and duplicate ids are ignored:

  1. Columns with isFinal: true (the enum is_final_option from the web hook).
  2. finalColumns — { id, label, color?, icon? }[].
  3. The presets: showDoneSection (KANBAN_DONE_COLUMN_ID = '__done__') and showArchiveSection (KANBAN_ARCHIVE_COLUMN_ID = '__archive__'). In flat mode, items with done: true / archived: true land in these presets.

A final zone lists the items assigned to it: value[finalId] in grouped mode, or items whose column id is the final id in flat mode.

API Reference

PropTypeDefaultDescription
columnsDocyKanbanColumn[]—Column definitions (order + metadata). isFinal columns become final zones. Optional in value mode
itemsT[]—Flat item list, grouped by getItemColumnId. Ignored when value is set
valueRecord<string, T[]>—Items grouped by column id (web value)
onValueChange(value: Record<string, T[]>) => void—Next grouped value (board columns only) after a committed change
onItemsChange(items: T[]) => void—Every item (board + final zones) in board order after a move
getItemValue(item: T) => string | numberitem.idItem id accessor
getItemColumnId(item: T) => stringitem.columnIdFlat-mode column accessor
columnOrderstring[]—Column order by id
onColumnOrderChange(columnIds: string[]) => void—New column order after a column reorder
variant'default' | 'outlined' | 'elevated''default'Column visual style
size'sm' | 'md' | 'lg''md'Column width (288 / 320 / 384) and card padding
showCountsbooleantrueShow the live item count in each column header
showDoneSectionbooleanfalseAdd the Done preset final zone
showArchiveSectionbooleanfalseAdd the Archive preset final zone
finalColumnsKanbanFinalColumn[]—Extra final zones
onFinalDrop(item: T, finalColumnId: string) => void—A card was dropped on a final zone
draggablebooleanfalseLong-press drag of cards
columnDraggablebooleandraggableLong-press drag of column headers
onCardPress(item: T) => void—Card tap
onItemClick(value: string | number) => void—Web-parity card tap with the item id
onAddCard(columnId: string) => void—Shows a + Add button under each column
onMoveCard(item: T, fromColumnId: string, toColumnId: string, toIndex: number, fromIndex: number) => void—Called after every card move, including final drops
onMove(event: KanbanMoveEvent<T>) => void—Web onMove. See Drag and drop
onDragStart(item: T, columnId: string) => void—A card drag activated
onDragEnd(item: T) => void—A card drag ended (dropped or cancelled)
renderCard(item: T, index: number, context: KanbanCardRenderContext) => ReactNode—Custom card (no default surface or padding; still draggable)
renderColumnHeader(column: DocyKanbanColumn, count: number) => ReactNode—Custom column header (still the column-drag handle)
classNamestring—Root view classes

All other View props (style, testID, …) are forwarded to the root view.

DocyKanbanColumn

A superset of the web hook's DocyrusKanbanColumnMeta.

FieldTypeDescription
idstringColumn id
titlestringHeader label (legacy native name)
labelstringHeader label (web name). Wins over title
colorstring | nullAccent color (header dot / final-zone tint)
iconstring | nullDocyrusIcon string shown in the header
imageUrlstring | nullAvatar shown in the header (user / team grouping)
isFinalbooleanRender as a final drop zone instead of a board column
countnumberInformational. The header shows the live item count

DocyKanbanItem

The default item shape, used by the built-in rich card.

FieldTypeDescription
idstring | numberUnique id
titlestringCard title
descriptionstringOptional description
columnIdstringColumn id (flat mode)
tagstringTag chip
priority'low' | 'medium' | 'high'Priority label
assigneestringAssignee name (initials avatar)
dueDateDate | stringDue-date chip (Today / Tomorrow / Nd left / Nd overdue)
donebooleanDone strike-through. Lands in the Done preset when showDoneSection is on
archivedbooleanLands in the Archive preset when showArchiveSection is on

KanbanFinalColumn

FieldTypeDescription
idstringFinal zone id (passed to onFinalDrop)
labelstringZone label
colorstring | nullTint
iconstring | nullDocyrusIcon string

KanbanMoveEvent

FieldTypeDescription
type'item' | 'column'Card move or column reorder
activeIdstring | numberMoved item id or column id
activeIndexnumberIndex before the move
overIndexnumberIndex after the move
fromColumnIdstringSource column
toColumnIdstringTarget column
itemTMoved item (type: 'item' only)

KanbanCardRenderContext

FieldTypeDescription
columnIdstringColumn (or final zone) the card is rendered in
isDraggingbooleanThe placeholder slot of the card being dragged
isOverlaybooleanRendering the floating drag overlay

Translations

KeyFallback
ui.common.todayToday
ui.kanban.tomorrowTomorrow
ui.kanban.daysLeft{count}d left
ui.kanban.overdue{count}d overdue
ui.kanban.noItemsNo items
ui.kanban.addCard+ Add
ui.kanban.doneDone
ui.kanban.archiveArchive
ui.kanban.dropToMarkFinalDrop to mark final

Components

ComponentDescription
DocyKanbanBoard component
KanbanWeb-parity alias of DocyKanban
KanbanCardContentThe built-in rich card body, for reuse inside a custom renderCard

Exports

ExportDescription
KANBAN_DONE_COLUMN_ID'__done__'
KANBAN_ARCHIVE_COLUMN_ID'__archive__'
withAlpha(color, alpha)Color helper (re-exported from the shared control styles)

Type Exports

TypeDescription
DocyKanbanProps<T>Props for DocyKanban
DocyKanbanColumnColumn metadata
KanbanColumnMetaWeb-parity alias of DocyKanbanColumn
DocyKanbanItemDefault item shape
KanbanFinalColumnFinal zone definition
KanbanMoveEvent<T>onMove payload
KanbanValue<T>Record<string, T[]>
KanbanIdstring | number
KanbanCardRenderContextThird renderCard argument

On this page