A feature-rich, customizable Kanban board component built for Expo and React Native. Perfect for task management, sales pipelines, project tracking, and any workflow that benefits from visual organization.
| π Drag-and-Drop | Smooth card movement between columns powered by Reanimated + Gesture Handler. |
| π¨ Fully Customizable | Colors, styles, and behavior tailored to your app via style props and render functions. |
| π± Responsive | Works flawlessly across iOS and Android mobile devices. |
| π’ Real-time Counts | Automatic tracking of items per column. |
| β»οΈ CRUD | Built-in support for moving items across columns via onDragEnd. |
| π¦ TypeScript | Fully typed components and props. |
| β‘ Performance | Built with Reanimated for buttery-smooth 60fps interactions. |
npm install react-native-expo-kanban react-native-reanimatedreact-native-reanimated is required for drag animations:
https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started
import ReactNativeKanbanBoard from "react-native-expo-kanban";
import {
KanbanBoardProps,
ItemParams,
HeaderParams,
} from "react-native-expo-kanban";
const columnData: KanbanBoardProps<ItemParams, HeaderParams>["columnData"] = [
{
header: { title: "Enquiry", subtitle: "Target 20 leads" },
items: [
{
id: "101",
first_name: "Bhageerathan",
last_name: "Pillai",
email: "Bhageerathan@Pillai.com",
phone: "+1 555-123-4567",
company: "Muthalali Inc.",
created_on: new Date().toISOString(),
priority: "high",
status: "New",
notes: "Interested in premium package",
assigned_to: {
first_name: "Ramanan",
last_name: "",
profile_pic: null,
},
},
{
id: "102",
first_name: "Kittunni",
last_name: "",
email: "kittunni@kilukkam.com",
phone: "+1 555-123-4567",
company: "Army Inc.",
created_on: new Date().toISOString(),
priority: "high",
status: "New",
notes: "Interested in premium package",
assigned_to: {
first_name: "Pankajakshan",
last_name: "Pillai",
profile_pic: null,
},
},
],
},
{
header: { title: "Interested", subtitle: "Make sure fixed" },
items: [
{
id: "104",
first_name: "Shaji",
last_name: "Pappan",
email: "ShajiPappan@Aadu.com",
phone: "+1 555-123-4567",
company: "Kalasamithi Inc.",
created_on: new Date().toISOString(),
priority: "high",
status: "New",
notes: "Interested in premium package",
assigned_to: {
first_name: "Eppan",
last_name: "Pappachi",
profile_pic: null,
},
},
],
},
];const renderItem = (item: any) => (
<View style={styles.card}>
<Text>{item.first_name}</Text>
<Text>{item.email}</Text>
</View>
);
const renderHeader = (header: any) => (
<View style={styles.header}>
<Text>{header.title}</Text>
<Text>{header.subtitle}</Text>
</View>
);<ReactNativeKanbanBoard
columnData={columnData}
renderItem={renderItem}
renderHeader={renderHeader}
columnHeaderStyle={styles.columnHeader}
columnContainerStyle={styles.columnContainer}
gapBetweenColumns={12}
columnWidth={SCREEN_WIDTH * 0.85}
columnContainerStyleOnDrag={styles.columnActive}
onDragEnd={({ fromColumnIndex, toColumnIndex, itemId }) => {
// update your state here and pass new columnData back
}}
/>| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
columnData |
columnData<T, K>[] |
[] |
β | Array of columns, each with header and items. |
renderItem |
(props: T, isDragged?: boolean) => JSX.Element |
β | β | Renders each card inside a column. |
renderHeader |
(props: K) => JSX.Element |
β | β | Renders the header for each column. |
onDragEnd |
(params: { fromColumnIndex, toColumnIndex, itemId }) => void |
undefined |
β | Fired when an item is dropped into a new column. |
columnHeaderStyle |
ViewStyle |
{} |
β | Style for the column header. |
columnContainerStyle |
ViewStyle |
{} |
β | Style for the column container. |
columnContainerStyleOnDrag |
ViewStyle |
0.7 |
β | Style applied while an item is being dragged into the column. |
gapBetweenColumns |
number |
8 |
β | Horizontal gap between columns. |
columnWidth |
number |
screen width Γ 0.8 | β | Width of each column. |
The package is nine source files under
src/. Data flows one way: the board reports indices/ids on drop, and the parent produces the new columns.
Board container generic over T extends ItemType and K. Computes screen constants, renders one horizontal FlatList per column, wires the drag gesture, and draws the floating preview.
| File | Role |
|---|---|
src/index.tsx |
Board container β column FlatList, drag wiring, floating preview |
src/components/DataCard.tsx |
Memoized Pressable card wrapper; measures itself on layout |
| Hook | Role |
|---|---|
useDragGesture |
Gesture lifecycle and drop decisions; calls onDragEnd on release |
useColumnPagination |
Throttled horizontal paging (left / right / re-center) |
useLayoutAnimation |
LayoutAnimation presets with Android workaround |
React context exposing the dragged card and shared drag coordinates (dragX, dragY). Provided by the board, consumed by cards/overlays via useDragContext.
long press
β DataCard.onLongPress
β measureInWindow()
β setDragCard({ props, x, y, width, columnIndex, id })
β gesture activates on touch move
β dragX / dragY shared values update
β near edge? paginate('left' | 'right')
β release
β drop()
β onDragEnd({ fromColumnIndex, toColumnIndex, itemId })
The board never mutates columnData. It reports the indices and item id, then waits for the parent to produce new columns β keeping the component predictable and compatible with any state library.
KanbanBoardProps, columnDataType, ItemType, DraggedCardProps, and example HeaderParams/ItemParams shapes.
Full architecture, props, types, hooks, and examples are hosted at:
π https://rnkanban.vercel.app
Contributions, issues, and feature requests are welcome!
Feel free to open a PR or file an issue on GitHub.
MIT Β© Naveen Mathramkott