Board (kanban columns)
Source: atrium/frontend/src/components/Board.jsx Category: Composite component
Board — Atrium’s primary view: four columns (todo, in_progress, review, done), each rendering a vertical stack of task cards. Drag-drop reorders cards within a column and moves them across columns; each move fires a PUT to update status.
What it is
Section titled “What it is”A grid layout with one column per status. Cards inside columns are rendered by TaskCard. The real Atrium Board wraps each card in a Draggable from @hello-pangea/dnd; the simplified version in the wiki strips the drag wiring and just shows the grouping.
Why it exists
Section titled “Why it exists”Kanban columns are the single most-used view. Having them as one component means column order, column labels, and empty-state behavior are consistent everywhere (main board, project-filtered view, swimlane groupings).
Keeping the drag logic separate from the visual layout means the same Board component works in read-only contexts (notifications, screenshots, embedded previews).
const columns = ['todo', 'in_progress', 'review', 'done'];
export default function Board({ tasks }) { const byStatus = groupBy(tasks, 'status'); return ( <div className="board-grid"> {columns.map(col => ( <div key={col} className="column"> <header>{labels[col]} <span>{byStatus[col]?.length ?? 0}</span></header> <div className="stack"> {byStatus[col]?.map(t => <TaskCard key={t.id} task={t} />)} </div> </div> ))} </div> );}The real Atrium Board adds:
- A
DragDropContextwrapping the grid Droppablearound each column’s stackDraggablearound each card, firing aPUT /api/tasks/:idwith the new status ononDragEnd- Swimlane grouping (by project, by assignee, by type) as a secondary axis
- Empty-column placeholders
How it’s used
Section titled “How it’s used”- Atrium — main view, list view fallback, project-filtered renders
- Pattern generalizes to any status-grouped list UI where the user reorganizes by dragging
Gotchas
Section titled “Gotchas”- Column list is canonical. Atrium’s four statuses aren’t configurable — adding one means updating the type, the backend, this component, and the CSS. If you want configurable columns per-project, model it as data; otherwise the hardcoded list keeps things honest.
- Drag-drop libraries choose the DOM shape.
@hello-pangea/dndrequires its own wrapper elements; you can’t put the drag handle on arbitrary children. Trying to customize the handle without reading the library’s docs ends in pain. - Move-between-columns is the interesting case. Drag within a column = reorder (usually tracked in a
positionfield). Drag across columns = status change. Handle both in oneonDragEnd. - Empty columns still need a dropzone. Without an empty-state target, users can’t drag anything into an empty column — feels broken. Always render the column wrapper even when it has zero cards.
- Virtualization breaks drag. For boards with thousands of cards, you want virtual scrolling; most DnD libraries don’t play nicely with it. Easier to paginate or hide low-priority done tasks than to force virtualization into the drag context.
- The simplified wiki render isn’t interactive. The live example above is layout-only — no dragging works. Useful for teaching the visual structure; not a drop-in replacement for the real thing.
See also
Section titled “See also”- components/task-card — the card rendered inside each column
- projects/atrium
- patterns/socket-io-live-state-fanout — what keeps the board in sync across browser tabs