KanbanBoard
AppTrello / Linear-style kanban board. M1 ships HTML5-native drag and drop between columns with an inline drop-position indicator (thin line between cards) and full optimistic-UI rewind on rejected onCardMove. Use the canMove hook to gate transitions client-side, or throw from onCardMove to revert. Future milestones: column reorder + collapse + WIP limits (M2), swimlanes + filters + search (M3), inline edit + bulk select + card detail panel (M4), keyboard nav + ARIA announcements (M5), virtualization + auto-archive + dependencies (M6). Pixel-identical EJS sibling at modules/app/KanbanBoard/KanbanBoard.ejs.
const columns = [
{ id: 'todo', title: 'To Do' },
{ id: 'doing', title: 'In Progress' },
{ id: 'done', title: 'Done' },
];
const [cards, setCards] = useState(initialCards);
<KanbanBoard
columns={columns}
cards={cards}
onCardMove={(card, from, to) =>
setCards((prev) =>
prev.map((c) => (c.id === card.id ? { ...c, columnId: to } : c)),
)
}
/><KanbanBoard
columns={columns}
cards={cards}
canMove={(card, from, to) => from !== 'done' || to === 'done'}
onCardMove={persist}
/><KanbanBoard
columns={columns}
cards={cards}
onCardMove={async (card, from, to) => {
const res = await fetch('/api/move', { method: 'POST', body: JSON.stringify({ card, to }) });
// Throw to revert the optimistic update.
if (!res.ok) throw new Error('Server rejected');
setCards(next);
}}
/>