TaskBoard

Horizontal status columns for the Tasks board view. Pair with TaskDnd for drag targets and TaskCard for items.

Preview#

import {
  TaskBoardColumn,
  TaskBoardColumnBadge,
  TaskBoardColumnChevron,
  TaskBoardColumnCount,
  TaskBoardColumnEmpty,
  TaskBoardColumnEmptyAllowed,
  TaskBoardColumnEmptyBlocked,
  TaskBoardColumnEmptyIdle,
  TaskBoardColumnHeading,
  TaskBoardColumnItems,
  TaskBoardColumnPanel,
  TaskBoardColumnTrigger,
  TaskBoardRoot,
  TaskCard,
  TaskDndProvider,
  TaskDragOverlay,
  TaskDraggable,
  TaskDropPlaceholder,
  TaskDropZone,
} from "raft-ui";

<TaskDndProvider isDropAllowed={() => true} onMove={() => {}}>
  <TaskBoardRoot>
    <TaskBoardColumn
      render={<TaskDropZone id="task-status:todo" data={{ status: "todo" }} />}
    >
      <TaskBoardColumnTrigger>
        <TaskBoardColumnHeading>
          <TaskBoardColumnBadge status="todo" />
          <TaskBoardColumnCount />
        </TaskBoardColumnHeading>
        <TaskBoardColumnChevron />
      </TaskBoardColumnTrigger>
      <TaskBoardColumnPanel>
        <TaskBoardColumnItems>
          <TaskBoardColumnEmpty>
            <TaskBoardColumnEmptyIdle />
            <TaskBoardColumnEmptyAllowed />
            <TaskBoardColumnEmptyBlocked />
          </TaskBoardColumnEmpty>
          <TaskDropPlaceholder id="task-1" placement="before" />
          <TaskDraggable
            id="task-1"
            dropId="task-1"
            dropData={{ status: "todo", taskId: "task-1" }}
          >
            <TaskCard />
          </TaskDraggable>
          <TaskDropPlaceholder id="task-1" placement="after" />
        </TaskBoardColumnItems>
      </TaskBoardColumnPanel>
    </TaskBoardColumn>
  </TaskBoardRoot>
  <TaskDragOverlay>
    <TaskCard />
  </TaskDragOverlay>
</TaskDndProvider>

Board with drag and drop#

Wrap columns in TaskDndProvider and mount each column as a TaskDropZone. Drop policy and move handlers stay in the composition.

Scroll area#

Keep one ScrollAreaViewport around the whole board. The viewport owns vertical and horizontal scrolling; TaskBoardRoot keeps its natural minimum width, so columns never become independent scroll regions.

Column empty states#

Sparse tasks leave In Review / Done / Closed empty so TaskBoardColumnEmptyIdle shows. Drag onto an empty column for Allowed / Blocked copy (Closed is not blocked in this demo — see TaskDnd).

Virtualization#

Keep one shared ScrollAreaViewport, and mount a useVirtualizer stack inside each tall column. Only viewport rows plus overscan stay mounted — watch the counter while scrolling. Virtualization stays consumer-owned; TaskBoard does not provide it.

Mount a virtual stack when a column can grow past the shared viewport. Only visible rows plus overscan stay in the DOM.

Mounted 0 / 48

API#WIP

PropTypeDefaultDescription
...propsuseRender.ComponentProps<"div">Horizontal board layout root.
PropTypeDefaultDescription
openbooleanControlled collapsed state for the column.
defaultOpenbooleanfalseInitial collapsed state for the column.
...propsCollapsible.Root.PropsFixed-width board column root. Compose TaskDropZone via render for DnD.
PropTypeDefaultDescription
...propsCollapsible.Trigger.PropsExpands or collapses the board column.
PropTypeDefaultDescription
...propsuseRender.ComponentProps<"span">Heading row for column badge and count.
PropTypeDefaultDescription
status"todo" | "in-progress" | "in-review" | "done" | "closed"Task status that drives the badge surface color.
...propsuseRender.ComponentProps<"span">Column status badge.
PropTypeDefaultDescription
...propsuseRender.ComponentProps<"span">Task count in the column heading.
PropTypeDefaultDescription
...propsuseRender.ComponentProps<"span">Disclosure chevron for the column trigger.
PropTypeDefaultDescription
...propsCollapsible.Panel.PropsCollapsible panel hosting column items and empty states.
PropTypeDefaultDescription
...propsuseRender.ComponentProps<"div">Item list inside a board column.
PropTypeDefaultDescription
...propsuseRender.ComponentProps<"div">Empty-column host for idle, allowed, and blocked drop copy.
PropTypeDefaultDescription
...propsuseRender.ComponentProps<"span">Idle empty-column message.
PropTypeDefaultDescription
...propsuseRender.ComponentProps<"span">Allowed drop-target empty-column message.
PropTypeDefaultDescription
...propsuseRender.ComponentProps<"span">Blocked drop-target empty-column message.