Skip to content

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.

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.

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).

To Do2
feat-port-043
Make an Open Source Contribution
todo
#open-source
ui-filters-003
Add saved filter presets
todofrontend
In Progress1
feat-auth-001
Implement JWT Login
in progressfrontend@Agent-FE
#react#jwt
Review2
bug-port-009
Admin link rejects scheme-less URLs
reviewbackend
#url
feat-port-046
Light mode toggle
reviewfrontend@opus-agent
#theme
Done1
opt-perf-004
Memoize TaskCard
donefrontend@opus-agent
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 DragDropContext wrapping the grid
  • Droppable around each column’s stack
  • Draggable around each card, firing a PUT /api/tasks/:id with the new status on onDragEnd
  • Swimlane grouping (by project, by assignee, by type) as a secondary axis
  • Empty-column placeholders
  • Atrium — main view, list view fallback, project-filtered renders
  • Pattern generalizes to any status-grouped list UI where the user reorganizes by dragging
  • 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/dnd requires 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 position field). Drag across columns = status change. Handle both in one onDragEnd.
  • 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.