Board
A kanban board with drag-and-drop between columns — Attio's Deals pipeline surface.
The kanban behind Attio's Deals pipeline: horizontally scrolling columns (a dot + title + live count header, a "New" button, a stack of cards) with native drag-and-drop between columns. The parent owns the cards, so moving one is a controlled state update — drag a card below and drop it in another column.
Preview
Installation
bun add crispimport "crisp/styles.css";
import { Board } from "crisp";Usage
import { Board, type BoardCard } from "crisp";
type Task = BoardCard & { title: string };
const [cards, setCards] = useState<Task[]>([
{ id: "t1", columnId: "todo", title: "Draft the proposal" },
{ id: "t2", columnId: "doing", title: "Design the deck" },
]);
<Board
columns={[
{ id: "todo", title: "To do", accent: "#8a94a6" },
{ id: "doing", title: "In progress", accent: "#266df0" },
{ id: "done", title: "Done", accent: "#22a565" },
]}
cards={cards}
onCardsChange={setCards}
onNewCard={(columnId) => setCards((cs) => [...cs, newTask(columnId)])}
newCardLabel="New task"
renderCard={(card) => <span>{card.title}</span>}
/>;API
Board<T>
| Prop | Type | Default | Description |
|---|---|---|---|
columns | BoardColumn[] | — | The columns, in order. Each is { id, title, accent? }. |
cards | T[] | — | The cards (controlled). Each extends { id, columnId }. |
onCardsChange | (cards: T[]) => void | — | Fires after a drag with the reparented card list. |
renderCard | (card: T) => ReactNode | — | Renders a card's body. |
onNewCard | (columnId: string) => void | — | When set, each column shows a header + and a "New" button. |
newCardLabel | string | "New" | Label for the per-column new-card button. |
BoardColumn is { id: string; title: string; accent?: string } (accent is a CSS colour for the header dot). BoardCard is { id: string; columnId: string } — extend it with your own fields.
Notes
- Drag-and-drop. Cards are native
draggableelements; dropping one on a column reparents it throughonCardsChange. The hovered drop column highlights while dragging. - Controlled. The board never mutates its own cards — it asks the parent to, so a board and a table can be two views over the same list of records.
- Counts are live. Each column header shows its current card count, derived from
cards.