Kanban

A Kanban board is a visual representation of work processes used to manage and optimize workflow.

Import

The Kanban board is composed of several key primitives:

  • Kanban: The canvas or workspace that provides context.
  • KanbanColumn: Columns represent different stages or phases in your workflow.
  • KanbanColumnHeader: The header of a column.
  • KanbanColumnBody: The body of a column.
  • KanbanColumnDragHandle: Handle to allow sorting of columns.
  • KanbanCard: A card represents an individual work item or task.
  • KanbanDragOverlay: A drag overlay that can be used to provide visual feedback.
  • KanbanTrash: Allows cards to be dropped and removed.
  • useKanbanContext: A hook to access the Kanban context.
import {
Kanban,
KanbanColumn,
KanbanColumnHeader,
KanbanColumnHandle,
KanbanCard,
KanbanTrash,
useKanbanContext,
} from '@saas-ui-pro/kanban'

Usage

These Kanban primitives can be used to build advanced workflows that allows items to be moved over different stages.

Basic

This is a basic example showing a simple Kanban board with 3 columns.

import { Card, CardBody } from '@chakra-ui/react'
import {
Kanban,
KanbanColumn,
KanbanColumnHeader,
KanbanColumnBody,
KanbanColumnHandle,
KanbanCard,
KanbanDragOverlay,
} from '@saas-ui-pro/kanban'
const columnDefs: Record<string, { title: string }> = {
todo: {
title: 'Todo',
},
doing: {
title: 'Doing',
},
done: {
title: 'Done',
},
}
export default function Basic() {
return (
<Kanban defaultItems={kanbanItems}>
{({ columns, items, activeId }) => {
return (
<>
{columns.map((columnId) => (
<KanbanColumn id={columnId} minWidth="200px">
<KanbanColumnHeader>
{columnDefs[columnId]?.title} ({items[columnId]?.length})
</KanbanColumnHeader>
<KanbanColumnBody>
{items[columnId].map((itemId) => {
return (
<KanbanCard id={itemId}>
<Card minHeight="80px" w="full" fontSize="sm">
<CardBody>{itemId}</CardBody>
</Card>
</KanbanCard>
)
})}
</KanbanColumnBody>
</KanbanColumn>
))}
<KanbanDragOverlay>
{activeId ? (
<KanbanCard id={activeId}>
<Card
minHeight="80px"
w="full"
fontSize="sm"
cursor="grabbing"
>
<CardBody>{activeId}</CardBody>
</Card>
</KanbanCard>
) : null}
</KanbanDragOverlay>
</>
)
}}
</Kanban>
)
}

Controlled

You can control the items by passing the items and onChange props to the Kanban component.

import { Card, CardBody } from '@chakra-ui/react'
import {
Kanban,
KanbanColumn,
KanbanColumnHeader,
KanbanColumnBody,
KanbanColumnHandle,
KanbanCard,
KanbanDragOverlay,
} from '@saas-ui-pro/kanban'
const columnDefs: Record<string, { title: string }> = {
todo: {
title: 'Todo',
},
doing: {
title: 'Doing',
},
done: {
title: 'Done',
},
}
export default function Controlled() {
const [items, setItems] = React.useState(kanbanItems)
return (
<Kanban items={items} onChange={setItems}>
{({ columns, items, activeId }) => {
return (
<>
{columns.map((columnId) => (
<KanbanColumn id={columnId} minWidth="200px">
<KanbanColumnHeader>
{columnDefs[columnId]?.title} ({items[columnId]?.length})
</KanbanColumnHeader>
<KanbanColumnBody>
{items[columnId].map((itemId) => {
return (
<KanbanCard id={itemId}>
<Card minHeight="80px" w="full" fontSize="sm">
<CardBody>{itemId}</CardBody>
</Card>
</KanbanCard>
)
})}
</KanbanColumnBody>
</KanbanColumn>
))}
<KanbanDragOverlay>
{activeId ? (
<KanbanCard id={activeId}>
<Card
minHeight="80px"
w="full"
fontSize="sm"
cursor="grabbing"
>
<CardBody>{activeId}</CardBody>
</Card>
</KanbanCard>
) : null}
</KanbanDragOverlay>
</>
)
}}
</Kanban>
)
}

Draggable columns

You can make columns draggable by using the KanbanColumnDragHandle component.

import { Card, CardBody, Spacer } from '@chakra-ui/react'
import {
Kanban,
KanbanColumn,
KanbanColumnProps,
KanbanColumnHeader,
KanbanColumnBody,
KanbanColumnDragHandle,
KanbanCard,
KanbanCardProps,
KanbanDragOverlay,
useKanbanContext,
} from '@saas-ui-pro/kanban'
const columnDefs: Record<string, { title: string }> = {
todo: {
title: 'Todo',
},
doing: {
title: 'Doing',
},
done: {
title: 'Done',
},
}
function DraggableBoardColumn({
children,
id,
style,
...props
}: KanbanColumnProps & {
id: string | number,
}) {
const { items } = useKanbanContext()
return (
<KanbanColumn id={id} minWidth="200px" {...props}>
<KanbanColumnHeader>
{columnDefs[id]?.title} ({items[id]?.length})
<Spacer />
<KanbanColumnDragHandle />
</KanbanColumnHeader>
<KanbanColumnBody>{children}</KanbanColumnBody>
</KanbanColumn>
)
}
function BoardCard({ id, ...rest }: Omit<KanbanCardProps, 'children'>) {
return (
<KanbanCard id={id} {...rest}>
<Card minHeight="100px" w="full" fontSize="sm">
<CardBody>{id}</CardBody>
</Card>
</KanbanCard>
)
}
export default function DraggableColumns() {
return (
<Kanban defaultItems={kanbanItems}>
{({ columns, items, isSortingColumn, activeId }) => {
function renderSortableItemDragOverlay(id: string | number) {
return <BoardCard id={id} cursor="grabbing" />
}
function renderColumnDragOverlay(columnId: string | number) {
return (
<KanbanColumn id={columnId} minWidth="200px">
{items[columnId].map((itemId) => (
<BoardCard id={itemId} key={itemId} />
))}
</KanbanColumn>
)
}
return (
<>
{columns.map((columnId) => (
<DraggableBoardColumn key={columnId} id={columnId}>
{items[columnId].map((itemId) => {
return (
<BoardCard
isDisabled={isSortingColumn}
key={itemId}
id={itemId}
/>
)
})}
</DraggableBoardColumn>
))}
<KanbanDragOverlay>
{activeId
? columns.includes(activeId)
? renderColumnDragOverlay(activeId)
: renderSortableItemDragOverlay(activeId)
: null}
</KanbanDragOverlay>
</>
)
}}
</Kanban>
)
}

Toggle columns

You can toggle column visibility by using useState.

import { Button, Card, CardBody } from '@chakra-ui/react'
import {
Kanban,
KanbanColumn,
KanbanColumnHeader,
KanbanColumnBody,
KanbanColumnHandle,
KanbanCard,
KanbanDragOverlay,
} from '@saas-ui-pro/kanban'
const columnDefs: Record<string, { title: string }> = {
todo: {
title: 'Todo',
},
doing: {
title: 'Doing',
},
done: {
title: 'Done',
},
}
export default function ToggleColumn() {
const [visibleColumns, setVisibleColumns] = React.useState({
todo: true,
doing: true,
done: false,
})
const showColumn = (columnId) => {
setVisibleColumns((prev) => ({
...prev,
[columnId]: true,
}))
}
const hideColumn = (columnId) => {
setVisibleColumns((prev) => ({
...prev,
[columnId]: false,
}))
}
const hiddenColumns = Object.keys(visibleColumns).filter(
(columnId) => !visibleColumns[columnId]
)
return (
<Kanban defaultItems={kanbanItems}>
{({ columns, items, isSortingColumn, activeId }) => {
return (
<>
{columns.map((columnId) => {
if (!visibleColumns[columnId]) {
return null
}
return (
<KanbanColumn id={columnId} minWidth="200px">
<KanbanColumnHeader py="1">
{columnDefs[columnId]?.title} ({items[columnId]?.length})
<Button onClick={() => hideColumn(columnId)} size="xs">
Hide
</Button>
</KanbanColumnHeader>
<KanbanColumnBody>
{items[columnId].map((itemId) => {
return (
<KanbanCard id={itemId}>
<Card minHeight="80px" w="full" fontSize="sm">
<CardBody>{itemId}</CardBody>
</Card>
</KanbanCard>
)
})}
</KanbanColumnBody>
</KanbanColumn>
)
})}
<Stack pt="10">
{hiddenColumns.map((col) => (
<Button onClick={() => showColumn(col)}>Show {col}</Button>
))}
</Stack>
<KanbanDragOverlay>
{activeId ? (
<KanbanCard id={activeId}>
<Card
minHeight="80px"
w="full"
fontSize="sm"
cursor="grabbing"
>
<CardBody>{activeId}</CardBody>
</Card>
</KanbanCard>
) : null}
</KanbanDragOverlay>
</>
)
}}
</Kanban>
)
}

Removing items

You can remove items by dragging them into the KanbanTrash component.

import { Button, Card, CardBody } from '@chakra-ui/react'
import {
Kanban,
KanbanColumn,
KanbanColumnHeader,
KanbanColumnBody,
KanbanColumnHandle,
KanbanCard,
KanbanDragOverlay,
KanbanTrash,
} from '@saas-ui-pro/kanban'
const columnDefs: Record<string, { title: string }> = {
todo: {
title: 'Todo',
},
doing: {
title: 'Doing',
},
done: {
title: 'Done',
},
}
export default function Trash() {
return (
<Kanban defaultItems={kanbanItems}>
{({ columns, items, isSortingColumn, activeId }) => {
return (
<>
{columns.map((columnId) => (
<KanbanColumn id={columnId} minWidth="200px">
<KanbanColumnHeader>
{columnDefs[columnId]?.title} ({items[columnId]?.length})
</KanbanColumnHeader>
<KanbanColumnBody>
{items[columnId].map((itemId) => {
return (
<KanbanCard id={itemId}>
<Card minHeight="80px" w="full" fontSize="sm">
<CardBody>{itemId}</CardBody>
</Card>
</KanbanCard>
)
})}
</KanbanColumnBody>
</KanbanColumn>
))}
<KanbanTrash mt="8">
<Card height="300px">
<CardBody>Drag items here to remove them.</CardBody>
</Card>
</KanbanTrash>
<KanbanDragOverlay>
{activeId ? (
<KanbanCard id={activeId}>
<Card
minHeight="80px"
w="full"
fontSize="sm"
cursor="grabbing"
>
<CardBody>{activeId}</CardBody>
</Card>
</KanbanCard>
) : null}
</KanbanDragOverlay>
</>
)
}}
</Kanban>
)
}

Add event listeners

The Kanban component provides several event listeners that can be used to listen to drag events.

  • onCardDragEnd: Called when a card drag ends.
  • onColumnDragEnd: Called when a column drag ends.
import { Button, Card, CardBody } from '@chakra-ui/react'
import {
Kanban,
KanbanColumn,
KanbanColumnHeader,
KanbanColumnBody,
KanbanColumnHandle,
KanbanCard,
KanbanDragOverlay,
KanbanTrash,
} from '@saas-ui-pro/kanban'
const columnDefs: Record<string, { title: string }> = {
todo: {
title: 'Todo',
},
doing: {
title: 'Doing',
},
done: {
title: 'Done',
},
}
export default function EventListeners() {
const onCardDragEnd: OnCardDragEndHandler = React.useCallback((args) => {
console.log(args)
}, [])
const onColumnDragEnd: OnColumnDragEndHandler = React.useCallback((args) => {
console.log(args)
}, [])
return (
<Kanban
defaultItems={kanbanItems}
onCardDragEnd={onCardDragEnd}
onColumnDragEnd={onColumnDragEnd}
>
{({ columns, items, isSortingColumn, activeId }) => {
return (
<>
{columns.map((columnId) => (
<KanbanColumn id={columnId} minWidth="200px">
<KanbanColumnHeader>
{columnDefs[columnId]?.title} ({items[columnId]?.length})
</KanbanColumnHeader>
<KanbanColumnBody>
{items[columnId].map((itemId) => {
return (
<KanbanCard id={itemId}>
<Card minHeight="80px" w="full" fontSize="sm">
<CardBody>{itemId}</CardBody>
</Card>
</KanbanCard>
)
})}
</KanbanColumnBody>
</KanbanColumn>
))}
<KanbanDragOverlay>
{activeId ? (
<KanbanCard id={activeId}>
<Card
minHeight="80px"
w="full"
fontSize="sm"
cursor="grabbing"
>
<CardBody>{activeId}</CardBody>
</Card>
</KanbanCard>
) : null}
</KanbanDragOverlay>
</>
)
}}
</Kanban>
)
}

Was this helpful?