Kanban
Horizontally scrollable kanban board with generic items, position-preserving drag, column reorder, final drop zones, and rich default cards.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-kanbanpnpm add react-native-gesture-handler react-native-reanimatedUsage
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.
| Change | Callbacks |
|---|---|
| Reorder inside a column | onMove(event) if set, otherwise onValueChange(next) (web semantics) · onMoveCard · onItemsChange |
| Move to another column | onValueChange(next) + onMove(event) · onMoveCard · onItemsChange |
| Drop on a final zone | onValueChange(next) (without the item) · onFinalDrop(item, finalColumnId) · onMoveCard · onItemsChange |
| Reorder columns | onColumnOrderChange(ids) · onMove(event) if set, otherwise onValueChange(next) (keys in the new order) |
Item sources
- Flat mode (
items): items are grouped withgetItemColumnId(defaultitem.columnId). With the default accessor, a moved item is returned with itscolumnIdupdated (anddone/archivedset for the presets). SoonItemsChange={setItems}keeps both the column AND the position. With a custom accessor, the item is returned untouched: useonValueChangeoronMoveCardto persist the move. - Grouped mode (
value): the webRecord<columnId, T[]>shape. It is authoritative for both membership and order.columnsis 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:
- Columns with
isFinal: true(the enumis_final_optionfrom the web hook). finalColumns—{ id, label, color?, icon? }[].- The presets:
showDoneSection(KANBAN_DONE_COLUMN_ID='__done__') andshowArchiveSection(KANBAN_ARCHIVE_COLUMN_ID='__archive__'). In flat mode, items withdone: true/archived: trueland 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
| Prop | Type | Default | Description |
|---|---|---|---|
columns | DocyKanbanColumn[] | — | Column definitions (order + metadata). isFinal columns become final zones. Optional in value mode |
items | T[] | — | Flat item list, grouped by getItemColumnId. Ignored when value is set |
value | Record<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 | number | item.id | Item id accessor |
getItemColumnId | (item: T) => string | item.columnId | Flat-mode column accessor |
columnOrder | string[] | — | 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 |
showCounts | boolean | true | Show the live item count in each column header |
showDoneSection | boolean | false | Add the Done preset final zone |
showArchiveSection | boolean | false | Add the Archive preset final zone |
finalColumns | KanbanFinalColumn[] | — | Extra final zones |
onFinalDrop | (item: T, finalColumnId: string) => void | — | A card was dropped on a final zone |
draggable | boolean | false | Long-press drag of cards |
columnDraggable | boolean | draggable | Long-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) |
className | string | — | Root view classes |
All other View props (style, testID, …) are forwarded to the root view.
DocyKanbanColumn
A superset of the web hook's DocyrusKanbanColumnMeta.
| Field | Type | Description |
|---|---|---|
id | string | Column id |
title | string | Header label (legacy native name) |
label | string | Header label (web name). Wins over title |
color | string | null | Accent color (header dot / final-zone tint) |
icon | string | null | DocyrusIcon string shown in the header |
imageUrl | string | null | Avatar shown in the header (user / team grouping) |
isFinal | boolean | Render as a final drop zone instead of a board column |
count | number | Informational. The header shows the live item count |
DocyKanbanItem
The default item shape, used by the built-in rich card.
| Field | Type | Description |
|---|---|---|
id | string | number | Unique id |
title | string | Card title |
description | string | Optional description |
columnId | string | Column id (flat mode) |
tag | string | Tag chip |
priority | 'low' | 'medium' | 'high' | Priority label |
assignee | string | Assignee name (initials avatar) |
dueDate | Date | string | Due-date chip (Today / Tomorrow / Nd left / Nd overdue) |
done | boolean | Done strike-through. Lands in the Done preset when showDoneSection is on |
archived | boolean | Lands in the Archive preset when showArchiveSection is on |
KanbanFinalColumn
| Field | Type | Description |
|---|---|---|
id | string | Final zone id (passed to onFinalDrop) |
label | string | Zone label |
color | string | null | Tint |
icon | string | null | DocyrusIcon string |
KanbanMoveEvent
| Field | Type | Description |
|---|---|---|
type | 'item' | 'column' | Card move or column reorder |
activeId | string | number | Moved item id or column id |
activeIndex | number | Index before the move |
overIndex | number | Index after the move |
fromColumnId | string | Source column |
toColumnId | string | Target column |
item | T | Moved item (type: 'item' only) |
KanbanCardRenderContext
| Field | Type | Description |
|---|---|---|
columnId | string | Column (or final zone) the card is rendered in |
isDragging | boolean | The placeholder slot of the card being dragged |
isOverlay | boolean | Rendering the floating drag overlay |
Translations
| Key | Fallback |
|---|---|
ui.common.today | Today |
ui.kanban.tomorrow | Tomorrow |
ui.kanban.daysLeft | {count}d left |
ui.kanban.overdue | {count}d overdue |
ui.kanban.noItems | No items |
ui.kanban.addCard | + Add |
ui.kanban.done | Done |
ui.kanban.archive | Archive |
ui.kanban.dropToMarkFinal | Drop to mark final |
Components
| Component | Description |
|---|---|
DocyKanban | Board component |
Kanban | Web-parity alias of DocyKanban |
KanbanCardContent | The built-in rich card body, for reuse inside a custom renderCard |
Exports
| Export | Description |
|---|---|
KANBAN_DONE_COLUMN_ID | '__done__' |
KANBAN_ARCHIVE_COLUMN_ID | '__archive__' |
withAlpha(color, alpha) | Color helper (re-exported from the shared control styles) |
Type Exports
| Type | Description |
|---|---|
DocyKanbanProps<T> | Props for DocyKanban |
DocyKanbanColumn | Column metadata |
KanbanColumnMeta | Web-parity alias of DocyKanbanColumn |
DocyKanbanItem | Default item shape |
KanbanFinalColumn | Final zone definition |
KanbanMoveEvent<T> | onMove payload |
KanbanValue<T> | Record<string, T[]> |
KanbanId | string | number |
KanbanCardRenderContext | Third renderCard argument |