
Syncfusion React Kanban
- 438 installs
- 3 repo stars
- Updated July 28, 2026
- syncfusion/react-ui-components-skills
Use syncfusion-react-kanban for development tasks
About
syncfusion-react-kanban: A skill for development. This provides functionality for development workflows.
- syncfusion-react-kanban
Syncfusion React Kanban by the numbers
- 438 all-time installs (skills.sh)
- +56 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #999 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/react-ui-components-skills --skill syncfusion-react-kanbanAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 438 |
|---|---|
| repo stars | ★ 3 |
| Last updated | July 28, 2026 |
| Repository | syncfusion/react-ui-components-skills ↗ |
What it does
Use syncfusion-react-kanban for development tasks
Files
Implementing Syncfusion React Kanban
A comprehensive skill for working with Syncfusion's React Kanban component to build flexible task management boards with cards, columns, swimlanes, and drag-and-drop interactions.
When to Use This Skill
Use this skill when you need to:
- Install and set up the Kanban component with Syncfusion
- Create a basic Kanban board with columns and cards
- Organize cards in swimlanes (group by category/assignee)
- Implement drag-and-drop functionality between columns and swimlanes
- Configure card displays with headers, content, templates, and tags
- Bind data from arrays or APIs (DataManager, OData, REST)
- Handle user interactions (card selection, drag events, double-click to edit)
- Add dialogs for card creation and editing
- Customize appearance with themes, CSS, and responsive design
- Validate card counts with WIP (work-in-progress) min/max limits
- Configure sorting, filtering, and search functionality
- Apply custom tooltips, templates, and styling
- Implement virtual scrolling for large datasets
- Save board state and configuration
Component Overview
The Kanban component provides a visual task management system with these core concepts:
Cards: Represent tasks or items. Each card displays a header (from a field) and content (from another field). Cards can have tags, custom templates, and be selected individually or in groups.
Columns: Define workflow stages (e.g., "To Do", "In Progress", "Done"). Each column has a header, can display item counts, and control drag/drop behavior independently.
Swimlanes: Optional horizontal grouping layers that organize cards by category (e.g., by user, priority, or team). Swimlanes help with organization and can have custom headers.
Drag-and-Drop: Built-in support for moving cards between columns, within columns, across swimlanes, or to external sources. Fully configurable per column.
Data Binding: Connect to static arrays or dynamic sources (DataManager with OData/REST APIs) for real-time updates.
Templates: Customize card layout, column headers, swimlane headers, tooltips, and dialogs with your own HTML/JSX.
Documentation and Navigation Guide
Choose the reference that matches your current need:
Getting Started
📄 Read: references/getting-started.md
- Installing @syncfusion/ej2-react-kanban
- CSS theme imports (Material, Bootstrap, Fluent, Tailwind)
- Basic Kanban component setup
- Configuring dataSource and field mappings
- First working example
Core Structure (Cards, Columns, Key Fields)
📄 Read: references/core-structure.md
- Card properties and cardSettings configuration
- Column definition and properties
- keyField mapping (linking cards to columns)
- showHeader, showAddButton, showItemCount
- Card templates and styling options
- When to use default vs custom card layouts
Swimlanes (Grouping Cards by Category)
📄 Read: references/swimlanes.md
- What swimlanes are and when to use them
- swimlaneSettings configuration
- Grouping cards by category, user, priority, or team
- Swimlane headers and custom rendering
- Nested vs flat layout variations
Drag-and-Drop (Moving Cards and Managing Flow)
📄 Read: references/drag-and-drop.md
- Internal drag-and-drop (column-to-column, within columns)
- External drag-and-drop (Kanban-to-Kanban, to external sources)
- allowDragAndDrop, allowDrag, allowDrop per column
- transitionColumns to control valid transitions
- Drag event handlers and preventing drops
Data Binding (Connecting to Data Sources)
📄 Read: references/data-binding.md
- Binding to static JSON arrays
- DataManager integration with OData and REST APIs
- Remote data sources and refresh patterns
- Modifying data and triggering updates
- Real-time data synchronization
Templates and Dialogs (Custom Content and Card Editing)
📄 Read: references/dialogs-and-templates.md
- Card templates (custom card layout)
- Column header templates
- Swimlane header templates
- Tooltip templates and configuration
- Dialog setup for adding/editing cards
- Template syntax and event handling
Sorting, Filtering, and Work-in-Progress (WIP) Validation
📄 Read: references/sorting-filtering-and-search.md
- Sort settings and card ordering
- Min/max card count per column (WIP limits)
- Allow sorting and searching
- Filtering and search patterns
- Selection modes (Single/Multiple)
- Handling validation errors
Customization and Styling (Themes, Responsive, Advanced Features)
📄 Read: references/customization-and-styling.md
- Built-in themes (Material, Bootstrap, Fluent, Tailwind)
- CSS customization and variables
- Responsive design behavior
- Virtual scrolling for large datasets
- Persistence (saving card state to storage)
- Localization (multi-language support)
- Accessibility (ARIA, keyboard navigation)
Properties (Configuration and Settings)
📄 Read: references/properties.md
- All configurable properties with type definitions
- cardSettings, columns, swimlaneSettings configuration
- Core properties (allowDragAndDrop, keyField, dataSource)
- Dialog settings and advanced options
- Complete configuration examples
- Decision trees for common configuration patterns
Methods (Programmatic Control and Actions)
📄 Read: references/methods.md
- Card management (addCard, updateCard, deleteCard)
- Column management (addColumn, deleteColumn, hideColumn, showColumn)
- Data access methods (getSelectedCards, getCardDetails, getColumnData, getSwimlaneData)
- UI control methods (showSpinner, hideSpinner, refreshUI, openDialog, closeDialog)
- Common programmatic patterns (bulk operations, reordering, duplication)
- Using refs to access methods from parent components
Events (Handling User Interactions and Data Changes)
📄 Read: references/events.md
- Card events (cardClick, cardDoubleClick, cardRendered, cardSelectionChanging)
- Dialog events (dialogOpen, dialogClose)
- Drag-drop events (dragStart, drag, dragStop)
- Data events (dataBinding, dataBound, actionBegin, actionComplete, actionFailure)
- Lifecycle events (created)
- Event validation and prevention patterns
Quick Start Example
Here's a minimal Kanban board with 3 columns and 5 cards:
import React from 'react';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
import '@syncfusion/ej2-react-kanban/styles/material.css';
export default function KanbanQuickStart(): JSX.Element {
const kanbanData: { [key: string]: Object } = [
{ Id: '1', Status: 'Open', Summary: 'Analyze requirements' },
{ Id: '2', Status: 'Open', Summary: 'Design UI mockups' },
{ Id: '3', Status: 'InProgress', Summary: 'Implement backend API' },
{ Id: '4', Status: 'InProgress', Summary: 'Build frontend components' },
{ Id: '5', Status: 'Closed', Summary: 'Deploy to production' }
];
return (
<KanbanComponent
dataSource={kanbanData}
keyField="Status"
cardSettings={{ contentField: 'Summary' }}
>
<ColumnsDirective>
<ColumnDirective key="0" keyField="Open" headerText="Open" />
<ColumnDirective key="1" keyField="InProgress" headerText="In Progress" />
<ColumnDirective key="2" keyField="Closed" headerText="Closed" />
</ColumnsDirective>
</KanbanComponent>
);
}What this does:
- Creates 3 workflow columns (Open, InProgress, Closed)
- Maps cards to columns using the
Statusfield - Displays 5 cards with their Summary text
- Enables drag-and-drop by default
Common Patterns
Pattern 1: Add Swimlanes to Group Cards by User
import { KanbanComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
<KanbanComponent
dataSource={kanbanData}
keyField="Status"
cardSettings={{ contentField: 'Summary' }}
swimlaneSettings={{ keyField: 'Assignee' }}
>
<ColumnsDirective>
<ColumnDirective keyField="Open" headerText="Open" />
<ColumnDirective keyField="InProgress" headerText="In Progress" />
<ColumnDirective keyField="Closed" headerText="Closed" />
</ColumnsDirective>
</KanbanComponent>Cards are now grouped horizontally by the Assignee field, making it easy to see each user's tasks.
Pattern 2: Prevent Drag-Drop in Specific Columns
import { ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
<ColumnsDirective>
<ColumnDirective keyField="Open" headerText="Open" allowDrag={true} allowDrop={true} />
<ColumnDirective keyField="Closed" headerText="Closed" allowDrag={false} allowDrop={false} />
</ColumnsDirective>Users can drag cards into "Open", but once in "Closed", cards are locked (read-only).
Pattern 3: Validate Card Counts with WIP Limits
import { ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
<ColumnsDirective>
<ColumnDirective
keyField="InProgress"
headerText="In Progress"
maxCount={5}
minCount={1}
/>
</ColumnsDirective>The board will warn users if column has fewer than 1 or more than 5 cards, enforcing work-in-progress limits.
Pattern 4: Handle Card Double-Click to Edit
import { CardClickEventArgs } from '@syncfusion/ej2-react-kanban';
const handleCardDoubleClick = (args: CardClickEventArgs): void => {
console.log('Card clicked:', args.data);
// Open a dialog or form to edit the card
};
<KanbanComponent
dataSource={kanbanData}
cardDoubleClick={handleCardDoubleClick}
>
{/* ... */}
</KanbanComponent>This pattern enables inline editing workflows.
Pattern 5: Bind Kanban to a Remote API
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
const dataManager: DataManager = new DataManager({
url: 'https://api.example.com/tasks',
adaptor: new UrlAdaptor()
});
<KanbanComponent
dataSource={dataManager}
keyField="Status"
cardSettings={{ contentField: 'Summary' }}
>
{/* ... */}
</KanbanComponent>Core Structure: Cards, Columns, and Key Fields
Table of Contents
- Overview
- Key Field Mapping
- Card Settings
- Column Configuration
- Single-Key vs Multi-Key Mapping
- Advanced Card Configuration
Overview
The Kanban board's core structure consists of three main concepts:
1. Key Field: The field in your data that determines column assignment 2. Cards: Visual items representing tasks, mapped to columns by their key field value 3. Columns: Workflow stages that organize cards horizontally
Understanding how these three work together is essential for building effective Kanban boards.
Key Field Mapping
The keyField property on KanbanComponent specifies which field in your data to use for column assignment:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { extend } from '@syncfusion/ej2-base';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from "@syncfusion/ej2-react-kanban";
import { kanbanData } from './datasource';
function App() {
let data = extend([], kanbanData, null, true);
return (
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));How it works:
- Kanban reads the
Statusfield from each data item - If
Status === 'Open', the card appears in the "To Do" column - If
Status === 'InProgress', the card appears in the "In Progress" column - If
Status === 'Closed', the card appears in the "Done" column
Example data:
const data = [
{ Id: 1, Status: 'Open', Summary: 'Task A' }, // → To Do column
{ Id: 2, Status: 'InProgress', Summary: 'Task B' }, // → In Progress column
{ Id: 3, Status: 'Closed', Summary: 'Task C' } // → Done column
];Card Settings
Configure what displays on each card using cardSettings:
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
<KanbanComponent
cardSettings={{
headerField: 'Id', // Field for card header
contentField: 'Summary', // Field for card body
showHeader: true, // Toggle header visibility
selectionType: 'Single', // Single or Multiple card selection
tagsField: 'Tags', // Field for tags display
grabberField: 'Priority' // Field for visual indicator
}}
>
{/* ... */}
</KanbanComponent>Required Fields
- headerField (MANDATORY): Must be unique for each card. Acts as card ID. Examples:
Id,TaskId,Ticket - contentField: The main text displayed in the card. Examples:
Summary,Title,Description
Optional Fields
- showHeader:
true/false— Display or hide the header section - selectionType:
'Single'(default) or'Multiple'— How many cards user can select - tagsField: Display comma-separated tags below content. Example:
'Tags'displays'bug,urgent' - grabberField: Visual indicator like priority color. Display color-coded field in left margin
Example with All Settings
import * as React from 'react';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
const data: = [
{
Id: 'PROJ-101',
Status: 'Open',
Summary: 'Design new dashboard',
Tags: 'design,ui',
Priority: 'High',
Assignee: 'Sarah'
},
{
Id: 'PROJ-102',
Status: 'InProgress',
Summary: 'Implement API endpoints',
Tags: 'backend,api',
Priority: 'Critical',
Assignee: 'John'
}
];
<KanbanComponent
dataSource={data}
keyField="Status"
cardSettings={{
headerField: 'Id',
contentField: 'Summary',
showHeader: true,
tagsField: 'Tags',
grabberField: 'Priority'
}}
>
<ColumnsDirective>
<ColumnDirective keyField="Open" headerText="To Do" />
<ColumnDirective keyField="InProgress" headerText="In Progress" />
</ColumnsDirective>
</KanbanComponent>Display result:
- Card header:
PROJ-101andPROJ-102 - Card content: "Design new dashboard" and "Implement API endpoints"
- Tags: "design,ui" and "backend,api"
- Grabber (left edge): Colored by Priority value
Column Configuration
Define columns with ColumnDirective:
import { ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
<ColumnsDirective>
<ColumnDirective
keyField="Open"
headerText="To Do"
showAddButton={true} // Show + button to add cards
showItemCount={true} // Show card count
allowDrag={true} // Allow dragging from this column
allowDrop={true} // Allow dropping into this column
maxCount={10} // Max cards allowed (WIP limit)
minCount={1} // Min cards required
/>
</ColumnsDirective>Column Properties
- keyField (required): Value to match in data's key field
- headerText: Display name for the column
- showAddButton: Display "+" button for adding new cards
- showItemCount: Display card count badge
- allowDrag: Enable/disable dragging from this column
- allowDrop: Enable/disable dropping into this column
- maxCount/minCount: Work-in-progress (WIP) limits
Stacked Headers
Stacked headers allow grouping multiple columns under a common header, providing a hierarchical organization of workflow stages.
Configuring Stacked Headers
Use the stackedHeaders property or the StackedHeadersDirective to group related columns:
import { KanbanComponent, StackedHeadersDirective, StackedHeaderDirective } from '@syncfusion/ej2-react-kanban';
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
>
<StackedHeadersDirective>
<StackedHeaderDirective text="To Do Details" keyFields="Open, Backlog" />
<StackedHeaderDirective text="Progress Details" keyFields="InProgress, Testing" />
</StackedHeadersDirective>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="Backlog" keyField="Backlog" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>Key Properties:
- text: The label displayed for the grouped columns.
- keyFields: A comma-separated string containing the
keyFieldvalues of the columns to be grouped.
Use Cases for Stacked Headers
- Grouping sub-stages of a large workflow (e.g., "Development" and "Testing" under "Engineering").
- Organizing multiple status columns under descriptive categories.
- Managing complex boards with many columns by adding a secondary header layer.
Single-Key vs Multi-Key Mapping
Single-Key Mapping (Default)
One column per key value:
<ColumnsDirective>
<ColumnDirective keyField="Open" headerText="To Do" />
<ColumnDirective keyField="InProgress" headerText="In Progress" />
<ColumnDirective keyField="Closed" headerText="Done" />
</ColumnsDirective>Data with Status: 'Open' goes to "To Do", Status: 'InProgress' goes to "In Progress", etc.
Multi-Key Mapping
Multiple key values in one column:
<ColumnsDirective>
<ColumnDirective keyField="Open, Backlog" headerText="To Do" />
<ColumnDirective keyField="InProgress" headerText="In Progress" />
<ColumnDirective keyField="Closed" headerText="Done" />
</ColumnsDirective>Cards with Status: 'Open' OR Status: 'Backlog' both appear in "To Do" column.
Use case: Group related statuses together (e.g., "New, Unassigned" → "Open" column)
Advanced Card Configuration
Hide Card Header
cardSettings={{
headerField: 'Id',
contentField: 'Summary',
showHeader: false // Only content displays
}}Cards display only the content field, no header section.
Handle Card Selection
Enable multi-select for bulk operations:
cardSettings={{
selectionType: 'Multiple'
}}Users click checkboxes to select multiple cards for batch actions.
Card-Level Customization with Methods
Add or update cards programmatically:
import * as React from 'react';
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
const kanbanRef = useRef<KanbanComponent>(null);
const handleAddCard = (): void => {
const newCard = {
Id: 'TASK-999',
Status: 'Open',
Summary: 'New task added'
};
kanbanRef.current?.addCard(newCard);
};
<KanbanComponent ref={kanbanRef}>
{/* ... */}
</KanbanComponent>Common Column Patterns
Read-only done column:
<ColumnDirective
keyField="Closed"
headerText="Done"
allowDrag={false}
allowDrop={false}
/>Accumulation with WIP limit:
<ColumnDirective
keyField="InProgress"
headerText="In Progress"
maxCount={5}
minCount={1}
/>Hidden column (visible but stacked):
<ColumnDirective
keyField="OnHold"
headerText="On Hold"
isExpanded={false} // Collapsed by default
/>Customization and Styling: Themes, Responsive Design, and Advanced Features
Table of Contents
- Built-in Themes
- CSS Customization
- CSS Variables
- Responsive Design
- Virtual Scrolling
- Persistence (Saving State)
- Localization
- Accessibility
Built-in Themes
Material Theme (Default)
@import '../node_modules/@syncfusion/ej2-kanban/styles/material.css';Modern, clean design inspired by Material Design.
Bootstrap Theme
@import '../node_modules/@syncfusion/ej2-kanban/styles/bootstrap.css';Bootstrap-compatible styling.
Bootstrap 4 Theme
@import '../node_modules/@syncfusion/ej2-kanban/styles/bootstrap4.css';Bootstrap 4 styling for modern layouts.
Fluent Theme
@import '../node_modules/@syncfusion/ej2-kanban/styles/fluent.css';Microsoft Fluent Design System styling.
Tailwind Theme
@import '../node_modules/@syncfusion/ej2-kanban/styles/tailwind3.css';Tailwind CSS compatible styling.
High Contrast Theme
@import '../node_modules/@syncfusion/ej2-kanban/styles/highcontrast.css';High contrast for accessibility and low-vision users.
CSS Customization
Override Default Styles
/* Customize card appearance */
.e-card {
background-color: #f5f5f5;
border-left: 4px solid #2196f3;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 6px;
transition: all 0.3s ease;
}
.e-card:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
/* Customize column header */
.e-column-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 600;
padding: 16px;
}
/* Customize swimlane header */
.e-swimlane-header {
background-color: #e8eaf6;
border-bottom: 2px solid #667eea;
font-weight: 600;
}
/* Card priority indicator */
.e-card.priority-critical {
border-left-color: #f44336;
}
.e-card.priority-high {
border-left-color: #ff9800;
}
.e-card.priority-medium {
border-left-color: #2196f3;
}
.e-card.priority-low {
border-left-color: #4caf50;
}Add Custom CSS Classes to Cards
import { ReactElement } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
// Card props expected: { Priority, Summary }
const cardTemplate = (props: any): ReactElement => {
const priorityClass = `priority-${props.Priority?.toLowerCase()}`;
return (
<div className={`custom-card ${priorityClass}`}>
<div>{props.Summary}</div>
</div>
);
};
<KanbanComponent cardSettings={{ template: cardTemplate }}>
{/* ... */}
</KanbanComponent>CSS Variables
Use CSS variables for dynamic theming:
:root {
--primary-color: #667eea;
--secondary-color: #764ba2;
--card-bg: #ffffff;
--card-border: #e0e0e0;
--header-bg: #f5f5f5;
--text-dark: #333333;
--text-light: #666666;
}
.e-kanban {
--primary: var(--primary-color);
--kanban-card-bg: var(--card-bg);
}
.e-card {
background-color: var(--card-bg);
border-left-color: var(--primary-color);
color: var(--text-dark);
}Runtime Theme Switching
const handleThemeChange = (theme: string): void => {
const root = document.documentElement;
if (theme === 'dark') {
root.style.setProperty('--card-bg', '#2a2a2a');
root.style.setProperty('--text-dark', '#ffffff');
root.style.setProperty('--header-bg', '#1a1a1a');
} else {
root.style.setProperty('--card-bg', '#ffffff');
root.style.setProperty('--text-dark', '#333333');
root.style.setProperty('--header-bg', '#f5f5f5');
}
};
<button onClick={() => handleThemeChange('dark')}>Dark Mode</button>
<button onClick={() => handleThemeChange('light')}>Light Mode</button>Responsive Design
Kanban adapts to different screen sizes automatically:
import * as React from 'react';
import { useState, useEffect } from 'react';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
export default function ResponsiveKanban() {
const [isMobile, setIsMobile] = useState<boolean>(window.innerWidth < 768);
useEffect(() => {
const handleResize = (): void => {
setIsMobile(window.innerWidth < 768);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// Hide less-critical columns on mobile
const getVisibleColumns = (): string[] => {
const visibleStatuses = isMobile
? ['Open', 'InProgress', 'Closed']
: ['Open', 'InProgress', 'Testing', 'Closed'];
return visibleStatuses;
};
return (
<KanbanComponent>
<ColumnsDirective>
{getVisibleColumns().map(status => (
<ColumnDirective key={status} keyField={status} />
))}
</ColumnsDirective>
</KanbanComponent>
);
}Mobile Card Adaptation
import { ReactElement } from 'react';
// Card props expected: { Id, Summary, Priority, Assignee }
const cardTemplate = (props: any): ReactElement => {
const isMobile = window.innerWidth < 768;
if (isMobile) {
// Compact card for mobile
return (
<div style={{ padding: '8px' }}>
<div style={{ fontWeight: 'bold', fontSize: '13px' }}>{props.Id}</div>
<div style={{ fontSize: '12px', marginTop: '4px' }}>{props.Summary}</div>
</div>
);
}
// Full card for desktop
return (
<div style={{ padding: '12px' }}>
<div style={{ fontWeight: 'bold' }}>{props.Id}</div>
<div>{props.Summary}</div>
<div style={{ marginTop: '8px', display: 'flex', gap: '8px' }}>
<span>{props.Priority}</span>
<span>{props.Assignee}</span>
</div>
</div>
);
};Virtual Scrolling
Enable for large datasets to improve performance:
// Data shape: { Id, Summary }
<KanbanComponent
enableVirtualization={true}
cardSettings={{
headerField: 'Id',
contentField: 'Summary'
}}
>
{/* Only visible cards render, scrolling loads more */}
</KanbanComponent>Benefits:
- Handles 1000+ cards smoothly
- Reduces DOM nodes by rendering only visible cards
- Improves scroll performance
Trade-offs:
- May have slight scroll jump initially
- Not ideal for animations across all cards
Persistence (Saving State)
Save Kanban state to localStorage or database:
Save to localStorage
import * as React from 'react';
import { useState } from 'react';
import { KanbanComponent, DragEventArgs } from '@syncfusion/ej2-react-kanban';
// Card shape: { Id, Status, Summary }
export default function PersistentKanban() {
const [data, setData] = useState(() => {
const saved = localStorage.getItem('kanban-data');
return saved ? JSON.parse(saved) : initialData;
});
const handleDragStop = (args: DragEventArgs): void => {
// Update state and persist
const updated = data.map(card =>
card.Id === args.data.Id ? args.data : card
);
setData(updated);
localStorage.setItem('kanban-data', JSON.stringify(updated));
};
return (
<KanbanComponent
dataSource={data}
dragStop={handleDragStop}
>
{/* ... */}
</KanbanComponent>
);
}Save to Server via API
import { DragEventArgs } from '@syncfusion/ej2-react-kanban';
const handleDragStop = (args: DragEventArgs): void => {
// Save to server
fetch('/api/tasks/' + args.data.Id, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(args.data)
}).then(r => {
if (!r.ok) {
console.error('Save failed');
// Revert card position
args.cancel = true;
}
});
};Localization
Translate Kanban text to different languages:
import { L10n } from '@syncfusion/ej2-base';
// Define translations
L10n.load({
'fr': {
'kanban': {
'cardSaveButton': 'Enregistrer',
'cardDeleteButton': 'Supprimer',
'cardCancelButton': 'Annuler'
}
},
'es': {
'kanban': {
'cardSaveButton': 'Guardar',
'cardDeleteButton': 'Eliminar',
'cardCancelButton': 'Cancelar'
}
}
});
// Apply locale
<KanbanComponent locale="fr">
{/* Dialog buttons show French text */}
</KanbanComponent>Custom Translations
import { L10n } from '@syncfusion/ej2-base';
L10n.load({
'en': {
'kanban': {
'placeholder': 'Search tasks...',
'addButton': '+ Add Card',
'deleteConfirm': 'Are you sure?'
}
}
});Accessibility
ARIA Labels
import { ReactElement } from 'react';
// Card props expected: { Id, Summary, Priority }
const cardTemplate = (props: any): ReactElement => {
return (
<div
role="button"
tabIndex={0}
aria-label={`Task ${props.Id}: ${props.Summary}, Priority: ${props.Priority}`}
>
{props.Summary}
</div>
);
};Keyboard Navigation
- Tab: Navigate between cards
- Enter: Click/activate card
- Space: Select card (if multi-select)
- Arrow keys: Move focus within column
Screen Reader Support
// Data shape: { Id, Status, Summary }
<KanbanComponent
aria-label="Project task board with columns for workflow stages"
aria-describedby="kanban-description"
>
<div id="kanban-description" style={{ display: 'none' }}>
Drag and drop cards between columns to change task status.
Double-click a card to edit. Columns show task counts.
</div>
{/* ... */}
</KanbanComponent>Color Contrast
/* Ensure text is readable */
.e-card {
color: #212121; /* Dark text on light background */
background-color: #ffffff;
}
.e-card.dark-theme {
color: #ffffff; /* Light text on dark background */
background-color: #1e1e1e;
}High Contrast Mode Support
@media (prefers-contrast: more) {
.e-card {
border: 2px solid #000;
color: #000;
background-color: #fff;
}
}Data Binding: Connecting to Data Sources
Table of Contents
- Overview
- Static Array Binding
- DataManager with Remote APIs
- OData Services
- REST APIs
- Real-Time Updates
- Troubleshooting
Overview
Kanban supports two data binding approaches:
1. Local/Static: JavaScript array of objects 2. Remote: DataManager with OData, REST, or custom adaptors
Choose based on your data source and update frequency.
Static Array Binding
Simple Array Binding
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { extend } from '@syncfusion/ej2-base';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from "@syncfusion/ej2-react-kanban";
import { kanbanData } from './datasource';
function App() {
let data = extend([], kanbanData, null, true);
return (
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));Best for:
- Static data that doesn't change
- Initial prototypes
- Small datasets
Dynamic Updates with useState
import * as React from 'react';
import { useState } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
// Data shape: { Id, Status, Summary }
const [data, setData] = useState(initialData);
const handleAddCard = (): void => {
const newCard: { [key: string]: Object } = {
Id: Math.max(...data.map(d => d.Id)) + 1,
Status: 'Open',
Summary: 'New Task'
};
setData([...data, newCard]);
};
<KanbanComponent dataSource={data}>
{/* ... */}
</KanbanComponent>Best for:
- Local form submissions
- Client-side only changes
- Small datasets
DataManager with Remote APIs
Basic DataManager Setup
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { DataManager, ODataAdaptor } from '@syncfusion/ej2-data';
import { KanbanComponent, ColumnsDirective, ColumnDirective, DialogEventArgs } from "@syncfusion/ej2-react-kanban";
function App() {
let data = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/Kanban',
adaptor: new ODataAdaptor
});
function DialogOpen(args: DialogEventArgs): void {
args.cancel = true;
}
return (
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
allowDragAndDrop={false}
dialogOpen={DialogOpen}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));Key properties:
url: API endpointadaptor: Protocol handler (ODataAdaptor, UrlAdaptor, JsonAdaptor)- Automatically handles fetch, CRUD, and sync
OData Services
OData is a standardized protocol for data access. Use ODataAdaptor:
import { DataManager, ODataAdaptor } from '@syncfusion/ej2-data';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
import { DialogEventArgs } from '@syncfusion/ej2-react-kanban';
const data: DataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/Kanban',
adaptor: new ODataAdaptor(),
crossDomain: true // Required for cross-origin requests
});
const handleDialogOpen = (args: DialogEventArgs): void => {
args.cancel = true;
};
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
allowDragAndDrop={false}
dialogOpen={handleDialogOpen}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>OData query examples built-in:
- Filter:
?$filter=Status eq 'Open' - Sorting:
?$orderby=Priority descend - Paging:
?$skip=0&$top=50 - Selection:
?$select=Id,Summary,Status
DataManager automatically appends these based on component actions.
REST APIs
For custom REST endpoints, use UrlAdaptor:
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
const data: DataManager = new DataManager({
url: 'Home/DataSource',
updateUrl: 'Home/Update',
insertUrl: 'Home/Insert',
removeUrl: 'Home/Delete',
adaptor: new UrlAdaptor(),
crossDomain: true
});
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>Expected API Endpoints
GET /tasks
[
{ "Id": 1, "Status": "Open", "Summary": "Task A" },
{ "Id": 2, "Status": "InProgress", "Summary": "Task B" }
]POST /tasks/add
Request: { "Id": 3, "Status": "Open", "Summary": "New Task" }
Response: { "Id": 3, "Status": "Open", "Summary": "New Task" }PUT /tasks/update
Request: { "Id": 1, "Status": "InProgress", "Summary": "Task A" }
Response: { "Id": 1, "Status": "InProgress", "Summary": "Task A" }DELETE /tasks/delete?id=1
Response: { "success": true }Real-Time Updates
Refresh Data After External Changes
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
import { DataManager } from '@syncfusion/ej2-data';
const kanbanRef = useRef<KanbanComponent>(null);
const handleRefresh = (): void => {
if (kanbanRef.current?.dataSource instanceof DataManager) {
const dataManager = kanbanRef.current.dataSource as DataManager;
dataManager.executeQuery();
}
};<KanbanComponent ref={kanbanRef} dataSource={dataManager}> {/ ... /} </KanbanComponent>
<button onClick={handleRefresh}>Refresh Board</button>
### WebSocket-Based Updates
For real-time collaboration:
import { useEffect, useRef, useState } from 'react'; import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
// WebSocket update shape: { action: 'cardUpdated'|'cardAdded'|'cardDeleted', card?, cardId? } export default function RealtimeKanban(): JSX.Element { const [data, setData] = useState([]); const kanbanRef = useRef<KanbanComponent>(null);
useEffect(() => { // Initial data fetch fetch('/api/tasks').then(r => r.json()).then(setData);
// WebSocket for real-time updates const ws = new WebSocket('wss://api.example.com/kanban-live');
ws.onmessage = (event: MessageEvent): void => { const update: WebSocketUpdate = JSON.parse(event.data);
if (update.action === 'cardUpdated' && update.card) { setData(data.map(card => card.Id === update.card!.Id ? update.card! : card )); } else if (update.action === 'cardAdded' && update.card) { setData([...data, update.card]); } else if (update.action === 'cardDeleted' && update.cardId) { setData(data.filter(card => card.Id !== update.cardId)); } };
return () => ws.close(); }, []);
return ( <KanbanComponent dataSource={data} ref={kanbanRef}> {/ ... /} </KanbanComponent> ); }
### Poll Server for Changes
import { useEffect } from 'react';
// Data shape: { Id, Status, Summary } useEffect(() => { const interval = setInterval((): void => { fetch('/api/tasks') .then(r => r.json()) .then((tasks) => setData(tasks)) .catch(err => console.error('Failed to fetch tasks:', err)); }, 5000); // Refresh every 5 seconds
return () => clearInterval(interval); }, []);
## Troubleshooting
### No data showing
- Verify API endpoint returns valid JSON array
- Check network tab for API errors
- Confirm `keyField` matches field in API response
### CORS errors
- Add `crossDomain: true` to DataManager
- Ensure server allows cross-origin requests
- Check API has `Access-Control-Allow-Origin` headers
### Drag-drop not saving
- Verify `insertUrl`, `updateUrl`, `removeUrl` are configured
- Check API returns updated record with same Id
- Monitor network tab for CRUD request failures
### Data not refreshing
- Use `dataManager.executeQuery()` to manual refresh
- Set polling interval if server doesn't support real-time
- Check DataManager `requestType` for DELETE/UPDATE operations
### Performance issues with large datasets
- Implement server-side pagination with `pageSize`
- Use virtual scrolling (covered in customization-and-styling.md)
- Filter data on server before sending to client
Templates and Dialogs: Custom Content and Card Editing
Table of Contents
- Overview
- Card Templates
- Column Header Templates
- Swimlane Header Templates
- Tooltip Templates
- Dialog Configuration
- Dialog Custom Fields
- Dialog Templates
- Advanced Patterns
Overview
Templates let you customize how Kanban renders cards, headers, and dialogs. They accept JSX functions that return custom HTML. Dialogs enable adding and editing cards with form fields.
Card Templates
Replace default card layout with custom JSX:
Basic Card Template
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
import { ReactElement } from 'react';
// Card props expected: { Id, Summary, Priority, Type }
const cardTemplate = (props: any): ReactElement => {
return (
<div className="custom-card">
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>
Task #{props.Id}
</div>
<div style={{ fontSize: '14px', marginBottom: '8px' }}>
{props.Summary}
</div>
<div style={{ display: 'flex', gap: '5px' }}>
<span style={{
padding: '2px 8px',
backgroundColor: '#e3f2fd',
borderRadius: '3px',
fontSize: '12px'
}}>
{props.Priority}
</span>
<span style={{
padding: '2px 8px',
backgroundColor: '#f3e5f5',
borderRadius: '3px',
fontSize: '12px'
}}>
{props.Type}
</span>
</div>
</div>
);
};
<KanbanComponent
cardSettings={{
headerField: 'Id',
template: cardTemplate // Use custom template
}}
>
{/* ... */}
</KanbanComponent>Card with Assignee Avatar
import { ReactElement } from 'react';
// Card props expected: { Id, Summary, AssigneeId, Assignee, CompletionDate, EstimateHours }
const cardTemplate = (props: any): ReactElement => {
return (
<div className="card-with-avatar">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ fontWeight: 'bold' }}>Task {props.Id}</div>
<img
src={`/avatars/${props.AssigneeId}.jpg`}
alt={props.Assignee}
style={{ width: '24px', height: '24px', borderRadius: '50%' }}
title={props.Assignee}
/>
</div>
<div style={{ marginTop: '8px', fontSize: '13px' }}>
{props.Summary}
</div>
<div style={{ marginTop: '8px', display: 'flex', justifyContent: 'space-between', fontSize: '11px', color: '#666' }}>
<span>{props.CompletionDate}</span>
<span>{props.EstimateHours}h</span>
</div>
</div>
);
};Card with Progress Bar
import { ReactElement } from 'react';
// Card props expected: { Summary, CompletionPercentage }
const cardTemplate = (props: any): ReactElement => {
const completion = props.CompletionPercentage || 0;
return (
<div>
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>
{props.Summary}
</div>
<div style={{
height: '4px',
backgroundColor: '#e0e0e0',
borderRadius: '2px',
overflow: 'hidden'
}}>
<div style={{
height: '100%',
backgroundColor: completion < 50 ? '#ff9800' : completion < 100 ? '#2196f3' : '#4caf50',
width: `${completion}%`,
transition: 'width 0.3s'
}} />
</div>
<div style={{ fontSize: '11px', marginTop: '4px', color: '#666' }}>
{completion}% Complete
</div>
</div>
);
};Column Header Templates
Customize column header appearance:
import { ReactElement } from 'react';
const columnHeaderTemplate = (props: { [key: string]: string }): ReactElement => {
const cardCount = props.cardCount || 0;
const maxCount = props.maxCount || null;
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div>
<div style={{ fontWeight: 'bold' }}>{props.headerText}</div>
<div style={{ fontSize: '12px', color: '#999' }}>
{cardCount}{maxCount ? `/${maxCount}` : ''} items
</div>
</div>
{cardCount >= (maxCount || 0) && maxCount ? (
<span style={{ color: 'red', fontWeight: 'bold' }}>!</span>
) : null}
</div>
);
};
<ColumnsDirective>
<ColumnDirective
keyField="Open"
template={columnHeaderTemplate}
/>
</ColumnsDirective>Swimlane Header Templates
Customize swimlane row headers:
import { ReactElement } from 'react';
// Swimlane props expected: { cardCount, AssigneeId, Assignee, AssigneeName }
const swimlaneTemplate = (props: any): ReactElement => {
// Count tasks in this swimlane
const taskCount = props.cardCount || 0;
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', padding: '8px' }}>
<img
src={`/avatars/${props.AssigneeId}.png`}
alt={props.Assignee}
style={{ width: '32px', height: '32px', borderRadius: '50%' }}
/>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: '600' }}>{props.AssigneeName}</div>
<div style={{ fontSize: '12px', color: '#666' }}>
{taskCount} {taskCount === 1 ? 'task' : 'tasks'}
</div>
</div>
<div style={{
padding: '4px 12px',
backgroundColor: taskCount > 5 ? '#ffebee' : '#e8f5e9',
borderRadius: '4px',
fontSize: '12px',
fontWeight: 'bold'
}}>
{taskCount > 5 ? 'Overloaded' : 'OK'}
</div>
</div>
);
};
<KanbanComponent
swimlaneSettings={{
keyField: 'Assignee',
template: swimlaneTemplate
}}
>
{/* ... */}
</KanbanComponent>Tooltip Templates
Customize card tooltip on hover:
import { ReactElement } from 'react';
// Card props expected: { Summary, Priority, Assignee, DueDate }
const tooltipTemplate = (props: any): ReactElement => {
return (
<div style={{ padding: '10px' }}>
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>
{props.Summary}
</div>
<table style={{ fontSize: '12px', color: '#666' }}>
<tbody>
<tr>
<td><strong>Priority:</strong></td>
<td>{props.Priority}</td>
</tr>
<tr>
<td><strong>Assignee:</strong></td>
<td>{props.Assignee}</td>
</tr>
<tr>
<td><strong>Due:</strong></td>
<td>{props.DueDate}</td>
</tr>
</tbody>
</table>
</div>
);
};
<KanbanComponent
tooltipSettings={{
template: tooltipTemplate
}}
>
{/* ... */}
</KanbanComponent>Dialog Configuration
Double-click a card to open the default dialog for editing. Use showAddButton on columns to enable card addition:
import { useRef } from 'react';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
// Dialog field structure: { key, text, type, dropdownData? }
const kanbanRef = useRef<KanbanComponent>(null);
<KanbanComponent
id="kanban"
ref={kanbanRef}
keyField="Status"
dataSource={data}
cardSettings={{
headerField: 'Id',
contentField: 'Summary'
}}
dialogSettings={{
fields: [
{ key: 'Id', text: 'Task ID', type: 'TextBox' } as DialogField,
{ key: 'Summary', text: 'Summary', type: 'TextArea' } as DialogField,
{ key: 'Status', text: 'Status', type: 'DropDown' } as DialogField,
{ key: 'Priority', text: 'Priority', type: 'DropDown' } as DialogField,
{ key: 'Assignee', text: 'Assigned To', type: 'DropDown' } as DialogField
]
}}
>
<ColumnsDirective>
<ColumnDirective keyField="Open" headerText="Open" showAddButton={true} />
<ColumnDirective keyField="InProgress" headerText="In Progress" showAddButton={true} />
<ColumnDirective keyField="Closed" headerText="Closed" showAddButton={true} />
</ColumnsDirective>
</KanbanComponent>How to enable Add/Edit/Delete operations:
- Add Cards: Click the "+" (
showAddButton) on any column header - Edit Cards: Double-click any card to open edit dialog
- Delete Cards: Use
kanbanRef.current?.deleteCard(cardId)method or context menu
Dialog Custom Fields
Define custom fields with specific input types:
// Dialog field structure: { key, text, type, dropdownData? }
const dialogSettings = {
fields: [
{
key: 'Id',
text: 'Task ID',
type: 'TextBox'
} as DialogField,
{
key: 'Summary',
text: 'Description',
type: 'TextArea'
} as DialogField,
{
key: 'Status',
text: 'Workflow Status',
type: 'DropDown',
dropdownData: ['Open', 'InProgress', 'Testing', 'Closed']
} as DialogField,
{
key: 'Priority',
text: 'Priority Level',
type: 'DropDown',
dropdownData: ['Low', 'Medium', 'High', 'Critical']
} as DialogField,
{
key: 'EstimateHours',
text: 'Effort (hours)',
type: 'Numeric'
} as DialogField,
{
key: 'DueDate',
text: 'Due Date',
type: 'DateTime'
} as DialogField
]
};
<KanbanComponent dialogSettings={dialogSettings}>
{/* ... */}
</KanbanComponent>Field Types Available
- TextBox: Single-line text input
- TextArea: Multi-line text
- Numeric: Number input
- DropDown: Select list
- Date: Date picker
Dialog Templates
Replace default dialog with custom form:
import React, { useState, ReactElement } from 'react';
// Card data shape: { Id, Summary, Status }
const dialogTemplate = (props: any): ReactElement => {
const [formData, setFormData] = useState(props);
const handleChange = (field: keyof { [key: string]: Object }, value: string): void => {
setFormData({ ...formData, [field]: value });
};
return (
<div style={{ padding: '20px' }}>
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', fontWeight: 'bold', marginBottom: '5px' }}>
Task ID
</label>
<input
type="text"
disabled
value={formData.Id}
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ccc' }}
/>
</div>
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', fontWeight: 'bold', marginBottom: '5px' }}>
Summary
</label>
<textarea
value={formData.Summary}
onChange={(e): void => handleChange('Summary', e.target.value)}
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ccc', minHeight: '80px' }}
/>
</div>
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', fontWeight: 'bold', marginBottom: '5px' }}>
Status
</label>
<select
value={formData.Status}
onChange={(e): void => handleChange('Status', e.target.value)}
style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid #ccc' }}
>
<option value="Open">Open</option>
<option value="InProgress">In Progress</option>
<option value="Closed">Closed</option>
</select>
</div>
</div>
);
};
<KanbanComponent
dialogSettings={{
template: dialogTemplate
}}
>
{/* ... */}
</KanbanComponent>Advanced Patterns
Add Rich Text Editor to Dialog
import { RichTextEditorComponent } from '@syncfusion/ej2-react-richtexteditor';
import { ReactElement } from 'react';
// Card data shape: { Id, DetailedDescription }
const dialogTemplate = (props: any): ReactElement => {
return (
<div>
{/* ... other fields ... */}
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', fontWeight: 'bold', marginBottom: '5px' }}>
Detailed Description
</label>
<RichTextEditorComponent
value={props.DetailedDescription}
onChange={(e: any): void => { props.DetailedDescription = e.value; }}
/>
</div>
</div>
);
};Card Click to Open Modal
import { CardClickEventArgs } from '@syncfusion/ej2-react-kanban';
// Use CardClickEventArgs from Syncfusion library
const handleCardClick = (args: CardClickEventArgs): void => {
// Open custom modal instead of default dialog
showCustomEditModal(args.data);
};
<KanbanComponent cardClick={handleCardClick}>
{/* ... */}
</KanbanComponent>Prevent Dialog from Opening on Double-Click
import { DialogEventArgs } from '@syncfusion/ej2-react-kanban';
// Use DialogEventArgs from Syncfusion library
const handleDialogOpen = (args: DialogEventArgs): void => {
// Prevent default dialog
args.cancel = true;
// Open custom form or modal
openCustomEditForm(args.data);
};
<KanbanComponent dialogOpen={handleDialogOpen}>
{/* ... */}
</KanbanComponent>Drag-and-Drop: Moving Cards and Managing Flow
Table of Contents
- Overview
- Internal Drag-and-Drop
- External Drag-and-Drop
- Column-Level Control
- Handling Drag Events
- Transition Columns
- Common Patterns
Overview
Kanban's drag-and-drop allows users to move cards between columns, within columns, across swimlanes, or even to external sources. By default, drag-and-drop is enabled. Control it with:
- Global:
allowDragAndDropproperty onKanbanComponent - Per-column:
allowDragandallowDropon eachColumnDirective - Transition logic:
transitionColumnsto restrict card flow
Internal Drag-and-Drop
Column-to-Column Movement
Cards can be dragged from one column to another:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { extend } from '@syncfusion/ej2-base';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from "@syncfusion/ej2-react-kanban";
import { kanbanData } from './datasource';
function App() {
let data = extend([], kanbanData, null, true);
return (
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
allowDragAndDrop={true}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));Effect: User drags "Task A" from "To Do" to "In Progress", card's Status field updates to "InProgress".
Disable Drag-and-Drop
You can disable drag-and-drop functionality globally:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { extend } from '@syncfusion/ej2-base';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from "@syncfusion/ej2-react-kanban";
import { kanbanData } from './datasource';
function App() {
let data = extend([], kanbanData, null, true);
return (
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
allowDragAndDrop={false}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));Effect: Users cannot drag cards at all when allowDragAndDrop={false}.
Swimlane Drag and Drop
By default, Swimlane allows drag and drop across the columns within the swimlane row. You cannot drag cards across the swimlane rows by default.
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { extend } from '@syncfusion/ej2-base';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from "@syncfusion/ej2-react-kanban";
import { kanbanData } from './datasource';
function App() {
let data = extend([], kanbanData, null, true);
return (
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
swimlaneSettings={{ keyField: "Assignee" }}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));Effect: Cards stay within their assigned swimlane row when moved between columns.
External Drag-and-Drop
Kanban-to-Kanban
Drag cards between two Kanban boards:
import { useState } from 'react';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-kanban';
// Data shape: { Id, Status, Summary }
export default function MultiKanban(): JSX.Element {
const [backlogData, setBacklogData] = useState(initialBacklog);
const [activeData, setActiveData] = useState(initialActive);
return (
<div style={{ display: 'flex', gap: '20px' }}>
<div style={{ flex: 1 }}>
<h3>Backlog</h3>
<KanbanComponent dataSource={backlogData}>
{/* Columns */}
</KanbanComponent>
</div>
<div style={{ flex: 1 }}>
<h3>Active Sprint</h3>
<KanbanComponent dataSource={activeData}>
{/* Columns */}
</KanbanComponent>
</div>
</div>
);
}Cards can be dragged from Backlog to Active Sprint, moving the item's data source.
Kanban to External List
Drag cards to external HTML elements:
import * as React from 'react';
import { useRef, useState } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
import { DragEventArgs } from '@syncfusion/ej2-react-kanban';
// Data shape: { Id, Summary, Status }
export default function KanbanToExternal() {
const kanbanRef = useRef<KanbanComponent>(null);
const [deletedCards, setDeletedCards] = useState([]);
const handleDragStop = (args: DragEventArgs): void => {
// Check if dropped outside Kanban
if (!args.element.closest('.e-kanban')) {
setDeletedCards([...deletedCards, args.data]);
kanbanRef.current?.deleteCard(args.data);
}
};
return (
<div>
<KanbanComponent ref={kanbanRef} dragStop={handleDragStop}>
{/* Columns */}
</KanbanComponent>
<div style={{ marginTop: '20px', padding: '10px', border: '1px dashed red' }}>
<h3>Dropped Items ({deletedCards.length})</h3>
{deletedCards.map(card => <div key={card.Id}>{card.Summary}</div>)}
</div>
</div>
);
}Column-Level Control
Control drag-drop per column with allowDrag and allowDrop:
Read-Only Done Column
<ColumnDirective
keyField="Done"
headerText="Done"
allowDrag={false} // Cannot drag FROM this column
allowDrop={true} // CAN drop INTO this column
/>Cards can move into Done, but once there, they're locked and cannot be moved out.
Drop-Only Recycle Bin
<ColumnDirective
keyField="Deleted"
headerText="Archived"
allowDrag={true} // CAN drag FROM this column to restore
allowDrop={true} // CAN drop INTO this column
allowToggle={false} // Always visible
/>No Drag-Drop Column
<ColumnDirective
keyField="OnHold"
headerText="On Hold"
allowDrag={false}
allowDrop={false}
/>Cards stuck in this column until status manually updated or moved via method.
Handling Drag Events
Respond to drag operations with event handlers:
import { KanbanComponent, DragEventArgs, CardClickEventArgs } from '@syncfusion/ej2-react-kanban';
const handleDragStart = (args: DragEventArgs): void => {
console.log('Dragging card:', args.data.Id);
console.log('From column:', args.data.Status);
// Can prevent drag: args.cancel = true;
};
const handleDragStop = (args: DragEventArgs): void => {
console.log('Card dropped');
console.log('New status:', args.data.Status);
};
const handleCardDoubleClick = (args: CardClickEventArgs): void => {
console.log('Editing card:', args.data.Id);
};
<KanbanComponent
dragStart={handleDragStart}
dragStop={handleDragStop}
cardDoubleClick={handleCardDoubleClick}
>
{/* Columns */}
</KanbanComponent>Prevent Drag Based on Card Properties
import { DragEventArgs } from '@syncfusion/ej2-react-kanban';
const handleDragStart = (args: DragEventArgs): void => {
// Prevent dragging completed items
if (args.data.Status === 'Done') {
args.cancel = true;
}
};Prevent Drop Into Specific Column
import { DragEventArgs } from '@syncfusion/ej2-react-kanban';
const handleDragStop = (args: DragEventArgs): void => {
// Only urgent tasks can go to InProgress
if (args.data.Status === 'InProgress' && args.data.Priority !== 'Urgent') {
args.cancel = true;
alert('Only Urgent tasks can move to In Progress');
}
};Transition Columns
Restrict which columns accept cards from other columns:
<ColumnDirective
keyField="Open"
headerText="To Do"
transitionColumns={['InProgress']} // Can only transition to InProgress
/>
<ColumnDirective
keyField="InProgress"
headerText="In Progress"
transitionColumns={['Testing', 'Done']} // Can go to Testing or Done
/>
<ColumnDirective
keyField="Testing"
headerText="Testing"
transitionColumns={['Done', 'Open']} // Can go to Done or back to Open
/>Effect:
- "To Do" cards can drag to "In Progress" only
- "In Progress" cards can drag to "Testing" or "Done"
- "Testing" cards can drag to "Done" or back to "To Do" if issues found
Multi-Level Workflow
<ColumnDirective
keyField="Analysis"
transitionColumns={['Design']}
/>
<ColumnDirective
keyField="Design"
transitionColumns={['Development']}
/>
<ColumnDirective
keyField="Development"
transitionColumns={['Testing']}
/>
<ColumnDirective
keyField="Testing"
transitionColumns={['Deployed']}
/>Enforces strict left-to-right workflow with no skipping or backtracking.
Common Patterns
Approval Flow
// Items must go: Open → Review → Approved → Deployed
<ColumnDirective keyField="Open" transitionColumns={['Review']} />
<ColumnDirective keyField="Review" transitionColumns={['Approved']} />
<ColumnDirective keyField="Approved" transitionColumns={['Deployed']} />
<ColumnDirective keyField="Deployed" allowDrag={false} />Optional Steps
// Items can skip QA if directly deployed
<ColumnDirective keyField="Dev" transitionColumns={['QA', 'Prod']} />
<ColumnDirective keyField="QA" transitionColumns={['Prod']} />
<ColumnDirective keyField="Prod" allowDrag={false} />Blocking High-Risk Moves
import { DragEventArgs } from '@syncfusion/ej2-react-kanban';
const handleDragStop = (args: DragEventArgs): void => {
// Prevent moving to Live without approval
if (args.data?.Status === 'Live' && !args.data?.ApprovedBy) {
args.cancel = true;
alert('This item requires approval before going live');
}
};Audit Trail
import { DragEventArgs } from '@syncfusion/ej2-react-kanban';
// Card shape: { Id, Status }
// AuditRecord shape: { cardId, fromStatus, toStatus, timestamp, movedBy }
const handleDragStop = (args: DragEventArgs): void => {
const moveRecord = {
cardId: args.data?.Id || '',
fromStatus: args.previousStatus,
toStatus: args.data?.Status || '',
timestamp: new Date(),
movedBy: getCurrentUser()
};
// Log to audit table or API
fetch('/api/audit-trail', {
method: 'POST',
body: JSON.stringify(moveRecord)
});
};Kanban Events: Handling User Interactions and Data Changes
Table of Contents
Overview
Kanban events fire in response to user actions (clicks, drags, edits) and data changes. Hook into events to implement custom workflows, validation, logging, and integrations.
Import event types for proper TypeScript support:
import {
CardClickEventArgs,
DialogEventArgs,
DragEventArgs,
ActionEventArgs
} from '@syncfusion/ej2-react-kanban';Example:
const handleCardClick = (args: CardClickEventArgs): void => {
console.log('Card clicked:', args.data);
};
<KanbanComponent
cardClick={handleCardClick}
>
{/* ... */}
</KanbanComponent>Card Events
cardClick
Type: EmitType<CardClickEventArgs>
Fires when user single-clicks a card.
const handleCardClick = (args: CardClickEventArgs): void => {
console.log('Card:', args.data.Id);
console.log('Element:', args.element);
console.log('Mouse event:', args.event);
// Highlight card
args.element.style.outline = '2px solid blue';
};
<KanbanComponent cardClick={handleCardClick}>
{/* ... */}
</KanbanComponent>Event Arguments:
data: Card object clickedelement: DOM element of cardevent: MouseEventcancel: Set to true to prevent default actionname: Event name
Use when:
- Opening card details sidebar
- Showing card preview tooltip
- Collecting analytics
cardDoubleClick
Type: EmitType<CardClickEventArgs>
Fires when user double-clicks a card. By default, opens the edit dialog automatically.
import { CardClickEventArgs } from '@syncfusion/ej2-react-kanban';
const handleCardDoubleClick = (args: CardClickEventArgs): void => {
console.log('Editing:', args.data.Summary);
// Dialog opens automatically - no need to call openDialog()
// Prevent default dialog and open custom form instead
// args.cancel = true;
// showCustomEditForm(args.data);
};
<KanbanComponent cardDoubleClick={handleCardDoubleClick}>
{/* ... */}
</KanbanComponent>Event Arguments: Same as cardClick
Important Notes:
- The edit dialog opens automatically when a card is double-clicked
- To prevent the default dialog and use a custom form, set
args.cancel = true - Do NOT call
kanbanRef.current.openDialog()in this event - it's redundant
Use when:
- Logging edit attempts
- Preventing default dialog for certain cards
- Custom edit workflows
- Conditional editing (only admins can edit)
cardRendered
Type: EmitType<CardRenderedEventArgs>
Fires before each card renders. Allows customizing individual cards.
const handleCardRendered = (args: CardRenderedEventArgs): void => {
const card = args.data;
// Color-code by priority
if (card.Priority === 'Critical') {
args.element.style.backgroundColor = '#ffebee';
args.element.style.borderLeft = '5px solid #d32f2f';
} else if (card.Priority === 'High') {
args.element.style.borderLeft = '5px solid #ff9800';
}
// Add custom badge for overdue
if (new Date(card.DueDate) < new Date()) {
args.element.setAttribute('data-overdue', 'true');
}
};
<KanbanComponent cardRendered={handleCardRendered}>
{/* ... */}
</KanbanComponent>Event Arguments:
data: Card object being renderedelement: DOM element createdcancel: Set to true to skip rendering
Use when:
- Applying conditional styling
- Adding badges or indicators
- Highlighting based on field values
- Custom formatting
cardSelectionChanging
Type: EmitType<CardSelectionEventArgs>
Fires when card selection is about to change.
const handleSelectionChanging = (args: CardSelectionEventArgs): void => {
console.log('Selected:', args.selectedCards);
console.log('Previous:', args.previousSelection);
// Prevent deselecting the only card
if (args.previousSelection.length === 1 && args.selectedCards.length === 0) {
args.cancel = true;
}
};
<KanbanComponent cardSelectionChanging={handleSelectionChanging}>
{/* ... */}
</KanbanComponent>Dialog Events
dialogOpen
Type: EmitType<DialogEventArgs>
Fires before the add/edit dialog opens.
import { DialogEventArgs } from '@syncfusion/ej2-react-kanban';
const handleDialogOpen = (args: DialogEventArgs): void => {
console.log('Action:', args.requestType); // 'Add' or 'Edit'
console.log('Data:', args.data);
// Prevent dialog from opening
if (args.requestType === 'Edit' && !isUserManager()) {
args.cancel = true;
alert('Only managers can edit cards');
}
// Customize dialog behavior
if (args.requestType === 'Add') {
args.data.CreatedBy = currentUser;
args.data.CreatedDate = new Date();
}
};
<KanbanComponent dialogOpen={handleDialogOpen}>
{/* ... */}
</KanbanComponent>Event Arguments:
data: Card objectelement: Dialog elementrequestType: 'Add' or 'Edit'cancel: Set true to prevent opening
Use when:
- Validating before edit
- Pre-filling form fields
- Restricting edit access
- Custom dialog workflows
dialogClose
Type: EmitType<DialogEventArgs>
Fires when the dialog closes (after Add/Edit completes or is cancelled).
const handleDialogClose = (): void => {
console.log('Dialog closed');
// Refresh or sync state after dialog closes
refreshKanbanData();
};
<KanbanComponent dialogClose={handleDialogClose}>
{/* ... */}
</KanbanComponent>Note: The dialog closes automatically after a successful Add or Edit action. The requestType ('Add' or 'Edit') is available in dialogOpen event, not dialogClose.
Drag-Drop Events
dragStart
Type: EmitType<DragEventArgs>
Fires when dragging a card begins.
import { DragEventArgs } from '@syncfusion/ej2-react-kanban';
const handleDragStart = (args: DragEventArgs): void => {
const card = args.data;
console.log('Started dragging:', card.Id);
// Prevent dragging completed items
if (card.Status === 'Done') {
args.cancel = true;
}
// Prevent dragging critical items without approval
if (card.Priority === 'Critical' && !card.ApprovedBy) {
args.cancel = true;
alert('This item requires approval before moving');
}
};
<KanbanComponent dragStart={handleDragStart}>
{/* ... */}
</KanbanComponent>Event Arguments:
data: Card being draggedelement: Card DOM elementevent: Mouse/Pointer eventdropIndex: Target indexcancel: Set true to prevent drag
Use when:
- Validation before drag
- Preventing drag of certain cards
- Loggin drag events
drag
Type: EmitType<DragEventArgs>
Fires while dragging (continuously during drag).
const handleDrag = (args: DragEventArgs): void => {
// Called many times during drag - keep lightweight
console.log('Dragging...');
};
<KanbanComponent drag={handleDrag}>
{/* ... */}
</KanbanComponent>Use when:
- Creating custom drag feedback
- Calculating drop zones
- Lightweight animations
dragStop
Type: EmitType<DragEventArgs>
Fires when drag ends (card dropped or cancelled).
const handleDragStop = (args: DragEventArgs): void => {
const card = args.data;
const newStatus = card.Status;
console.log(`Moved ${card.Id} to ${newStatus}`);
// Prevent drop into Done without completion
if (newStatus === 'Done' && card.CompletionPercent < 100) {
args.cancel = true;
alert('Complete 100% of work before moving to Done');
return;
}
// Save to server
fetch(`/api/tasks/${card.Id}`, {
method: 'PUT',
body: JSON.stringify({ Status: newStatus })
});
};
<KanbanComponent dragStop={handleDragStop}>
{/* ... */}
</KanbanComponent>Event Arguments: Same as dragStart
Use when:
- Validating drop position
- Saving status changes
- Workflow enforcement
- Audit logging
Data Events
dataBinding
Type: EmitType<Object>
Fires before data is bound to Kanban (initial load or refresh).
const handleDataBinding = (): void => {
console.log('Data binding starting...');
// Show spinner
kanbanRef.current.showSpinner();
};
<KanbanComponent dataBinding={handleDataBinding}>
{/* ... */}
</KanbanComponent>Use when:
- Showing loading indicator
- Pre-fetching related data
- Tracking data loads
dataBound
Type: EmitType<Object>
Fires after data is bound and rendered.
const handleDataBound = (): void => {
console.log('Data binding complete');
// Hide spinner
kanbanRef.current.hideSpinner();
};
<KanbanComponent dataBound={handleDataBound}>
{/* ... */}
</KanbanComponent>Use when:
- Hiding loading spinner
- Post-processing rendered data
- Updating related UI
actionBegin
Type: EmitType<ActionEventArgs>
Fires at the beginning of any action (add, edit, delete, drag).
import { ActionEventArgs } from '@syncfusion/ej2-react-kanban';
const handleActionBegin = (args: ActionEventArgs): void => {
const action = args.requestType; // 'cardCreate', 'cardChange', 'cardDelete', etc.
console.log('Action:', action);
console.log('Added:', args.addedRecords);
console.log('Changed:', args.changedRecords);
console.log('Deleted:', args.deletedRecords);
// Prevent duplicate card creation
if (action === 'cardCreate') {
const isDuplicate = data.some(c => c.Id === args.addedRecords[0].Id);
if (isDuplicate) {
args.cancel = true;
}
}
};
<KanbanComponent actionBegin={handleActionBegin}>
{/* ... */}
</KanbanComponent>Event Arguments:
requestType: 'cardCreate', 'cardChange', 'cardDelete', 'cardDrop'addedRecords: New cardschangedRecords: Modified cardsdeletedRecords: Removed cardscancel: Set true to prevent action
Use when:
- Validation before actions
- Preventing invalid operations
- Logging all changes
actionComplete
Type: EmitType<ActionEventArgs>
Fires after action completes successfully.
import { ActionEventArgs } from '@syncfusion/ej2-react-kanban';
const handleActionComplete = (args: ActionEventArgs): void => {
const action = args.requestType;
console.log(`${action} completed`);
// Update related UI or refresh dependent data
if (action === 'cardDrop') {
refreshProjectStats();
}
};
<KanbanComponent actionComplete={handleActionComplete}>
{/* ... */}
</KanbanComponent>Use when:
- Refreshing dependent data
- Updating UI after changes
- Success notifications
actionFailure
Type: EmitType<ActionEventArgs>
Fires when an action fails (e.g., API call fails).
const handleActionFailure = (args: ActionEventArgs): void => {
console.error('Action failed:', args);
console.error('Error:', args.error);
alert('Could not save changes. Please try again.');
// Revert UI if needed
kanbanRef.current.refreshUI();
};
<KanbanComponent actionFailure={handleActionFailure}>
{/* ... */}
</KanbanComponent>Use when:
- Handling API errors
- Showing error messages
- Reverting changes on failure
dataSourceChanged
Type: EmitType<DataSourceChangedEventArgs>
Fires on create/update/delete for DataManager-based sources.
const handleDataSourceChanged = (args: DataSourceChangedEventArgs): void => {
const requestType = args.requestType;
if (requestType === 'save') {
// Save to server
setTimeout(() => {
args.done(); // Signal completion
}, 1000);
}
};
<KanbanComponent dataSourceChanged={handleDataSourceChanged}>
{/* ... */}
</KanbanComponent>Important: Must call args.done() when async operation completes.
Lifecycle Events
created
Type: EmitType<Object>
Fires when Kanban component is fully initialized.
const handleCreated = (): void => {
console.log('Kanban created and ready');
// Initialize custom features
loadUserPreferences();
applyCustomCSS();
};
<KanbanComponent created={handleCreated}>
{/* ... */}
</KanbanComponent>Use when:
- Setting up custom functionality
- Applying user preferences
- First-time initialization
Event Patterns
Pattern 1: Complete Card Validation
const handleActionBegin = (args: ActionEventArgs): void => {
if (args.requestType === 'cardCreate') {
const card = args.addedRecords[0];
// Validate: required fields
if (!card.Id || !card.Summary) {
args.cancel = true;
alert('ID and Summary are required');
return;
}
// Validate: summary length
if (card.Summary.length < 5) {
args.cancel = true;
alert('Summary must be at least 5 characters');
return;
}
}
};Pattern 2: Audit Trail
const auditLog: any[] = [];
const handleActionBegin = (args: ActionEventArgs): void => {
auditLog.push({
timestamp: new Date(),
action: args.requestType,
userId: currentUser,
cards: args.addedRecords || args.changedRecords || args.deletedRecords
});
};
const handleExportAudit = (): void => {
console.table(auditLog);
};Pattern 3: Real-Time Notification
const handleActionComplete = (args: ActionEventArgs): void => {
if (args.requestType === 'cardChange') {
args.changedRecords.forEach(card => {
notifyTeam(`Card ${card.Id} status changed to ${card.Status}`);
});
}
};Pattern 4: Prevent Work Overload
const handleDragStop = (args: DragEventArgs): void => {
const card = args.data;
const targetColumn = card.Status;
const columnCards = kanbanRef.current.getColumnData(targetColumn);
const assigneeCards = columnCards.filter(c =>
c.Assignee === card.Assignee
);
if (assigneeCards.length > 5) {
args.cancel = true;
alert(`${card.Assignee} already has 5+ items in ${targetColumn}`);
}
};Pattern 5: Smart Edit Prevention
const handleDialogOpen = (args: DialogEventArgs): void => {
if (args.requestType === 'Edit') {
const card = args.data;
const canEdit =
currentUser === card.Assignee ||
isManager(currentUser) ||
isAdmin(currentUser);
if (!canEdit) {
args.cancel = true;
alert('You can only edit tasks assigned to you');
}
}
};Getting Started with Syncfusion React Kanban
Table of Contents
Installation
Install the Kanban component package using npm:
npm install @syncfusion/ej2-react-kanbanThis installs the core Kanban component and its dependencies.
CSS Imports
Import the required CSS files in your src/App.css or main component file. Choose the theme that matches your design:
Tailwind Theme (Default)
@import '../node_modules/@syncfusion/ej2-base/styles/tailwind3.css';
@import '../node_modules/@syncfusion/ej2-buttons/styles/tailwind3.css';
@import '../node_modules/@syncfusion/ej2-dropdowns/styles/tailwind3.css';
@import '../node_modules/@syncfusion/ej2-inputs/styles/tailwind3.css';
@import '../node_modules/@syncfusion/ej2-layouts/styles/tailwind3.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css';
@import '../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css';
@import '../node_modules/@syncfusion/ej2-notifications/styles/tailwind3.css';
@import '../node_modules/@syncfusion/ej2-kanban/styles/tailwind3.css';Other Available Themes
- Material: Replace
tailwind3withmaterialin all imports - Bootstrap: Replace
tailwind3withbootstrapin all imports - Bootstrap4: Replace
tailwind3withbootstrap4 - Fluent: Replace
tailwind3withfluent - High Contrast: Replace
tailwind3withhighcontrast
Basic Setup
Import the required Kanban components and create your first board:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from "@syncfusion/ej2-react-kanban";
function App() {
return (
<KanbanComponent id="kanban" keyField="Status">
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));What this does:
- Creates a Kanban board with 3 workflow columns
keyField="Status"tells Kanban to look for aStatusfield in your data to determine which column a card belongs to- Each
ColumnDirectivedefines a column with a header and a key value
Data Binding
Connect your Kanban to data using the dataSource and cardSettings properties:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { extend } from '@syncfusion/ej2-base';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from "@syncfusion/ej2-react-kanban";
import { kanbanData } from './datasource';
function App(): JSX.Element {
const data = extend([], kanbanData, null, true);
return (
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));Key Properties:
dataSource: Array of objects or DataManager instance containing your cardskeyField: The field name that determines column mapping (e.g., "Status")cardSettings.headerField: The field displayed at the top of each cardcardSettings.contentField: The field displayed in the card body
First Working Example
Here's a complete, working example with sample data:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { extend } from '@syncfusion/ej2-base';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from "@syncfusion/ej2-react-kanban";
import { kanbanData } from './datasource';
function App(): JSX.Element {
// Data structure: { Id: string, Status: string, Summary: string }
const data = extend([], kanbanData, null, true);
return (
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));What you get:
- A 4-column Kanban board (To Do, In Progress, Testing, Done)
- Cards distributed across columns based on Status field
- Drag-and-drop enabled by default
- Clean, responsive layout
Enable Swimlane
Swimlanes allow you to group cards by categories (e.g., by assignee, priority, project). Enable swimlanes by mapping swimlaneSettings.keyField to a field in your data:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { extend } from '@syncfusion/ej2-base';
import { KanbanComponent, ColumnsDirective, ColumnDirective } from "@syncfusion/ej2-react-kanban";
import { kanbanData } from './datasource';
function App(): JSX.Element {
// Data structure: { Id: string, Status: string, Summary: string, Assignee: string }
const data = extend([], kanbanData, null, true);
return (
<KanbanComponent
id="kanban"
keyField="Status"
dataSource={data}
cardSettings={{ contentField: "Summary", headerField: "Id" }}
swimlaneSettings={{ keyField: "Assignee" }}
>
<ColumnsDirective>
<ColumnDirective headerText="To Do" keyField="Open" />
<ColumnDirective headerText="In Progress" keyField="InProgress" />
<ColumnDirective headerText="Testing" keyField="Testing" />
<ColumnDirective headerText="Done" keyField="Close" />
</ColumnsDirective>
</KanbanComponent>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('kanban'));Effect: Cards are now grouped by the Assignee field, creating separate rows for each assignee with their tasks across all columns.
Troubleshooting
Cards not showing
- Verify
keyFieldonKanbanComponentmatches the field name in your data - Ensure
cardSettings.headerFieldandcontentFieldpoint to existing fields - Check that column
keyFieldvalues match the data field values (e.g., "Open" in both)
Styles not applying
- Confirm CSS imports are in correct order (base → buttons → kanban)
- Check that CSS imports use correct path from
node_modules - Verify theme name is consistent (material, bootstrap, etc.)
Columns not rendering
- At least one
ColumnDirectivemust be declared - Each column must have both
headerTextandkeyField keyFieldmust match values in your data'skeyFieldproperty
Data not updating
- For static data, simply pass the array to
dataSource - For dynamic data, use
DataManager(covered in data-binding.md) - Import
extendfrom@syncfusion/ej2-baseto safely copy data without mutations
Kanban Methods: Programmatic Control and Actions
Table of Contents
- Overview
- Card Management Methods
- Column Management Methods
- Data Access Methods
- UI Control Methods
- Dialog Methods
- Common Patterns
Overview
Kanban methods allow you to programmatically control the board, manage cards and columns, access data, and manipulate the UI. Access methods via a ref to the KanbanComponent.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
const kanbanRef = useRef<KanbanComponent>(null);
// Later in code:
kanbanRef.current?.addCard(newCard);
kanbanRef.current?.deleteColumn(0);Card Management Methods
addCard()
Signature: addCard(cardData: Record | Record[], index?: number): void
Adds one or more cards to the Kanban board and data source.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
// Card shape: { Id, Status, Summary, Priority? }
const kanbanRef = useRef<KanbanComponent>(null);
const handleAddCard = (): void => {
const newCard = {
Id: 'TASK-100',
Status: 'Open',
Summary: 'New task',
Priority: 'High'
};
kanbanRef.current?.addCard(newCard);
};
// Add multiple cards
const handleAddMultiple = (): void => {
const cards = [
{ Id: 'T1', Status: 'Open', Summary: 'Task 1' },
{ Id: 'T2', Status: 'Open', Summary: 'Task 2' }
];
kanbanRef.current?.addCard(cards);
};Parameters:
cardData(required): Single card object or array of cardsindex(optional): Position in column to insert
Returns: void
Use when:
- User submits a form to create a new task
- Importing cards from CSV/API
- Programmatically populating the board
updateCard()
Signature: updateCard(cardData: Record | Record[], index?: number): void
Updates existing cards with new values.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
// Card shape: { Id, Status, Summary, Priority? }
const kanbanRef = useRef<KanbanComponent>(null);
const handleUpdateCard = (cardId: string, newValues: Record<string, any>): void => {
const cardToUpdate: KanbanCard = {
Id: cardId,
Status: 'InProgress',
Summary: 'Updated task'
};
kanbanRef.current?.updateCard(cardToUpdate);
};
// Update multiple cards at once
const handleBulkUpdate = (): void => {
const updates: Partial<KanbanCard>[] = [
{ Id: 'T1', Priority: 'High' } as KanbanCard,
{ Id: 'T2', Priority: 'Critical' } as KanbanCard
];
kanbanRef.current?.updateCard(updates);
};Parameters:
cardData: Card to update (must include original ID/headerField)index(optional): Position to move card
Returns: void
Important: Changes made to card objects directly may not update the UI. Always use updateCard() to ensure proper refresh.
deleteCard()
Signature: deleteCard(cardData: string | number | Record | Record[]): void
Removes one or more cards from the Kanban and data source.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
interface KanbanCard {
Id: string;
[key: string]: any;
}
const kanbanRef = useRef<KanbanComponent>(null);
const handleDeleteCard = (cardId: string): void => {
// Delete by ID
kanbanRef.current?.deleteCard(cardId);
};
// Delete by card object
const handleDeleteByObject = (card: any): void => {
kanbanRef.current?.deleteCard(card);
};
// Delete multiple cards
const handleBulkDelete = (): void => {
const cardIds = ['T1', 'T2', 'T3'];
cardIds.forEach(id => kanbanRef.current?.deleteCard(id));
};
// Delete selected cards
const handleDeleteSelected = (): void => {
const selected = kanbanRef.current?.getSelectedCards() || [];
selected.forEach(element => {
const cardData = kanbanRef.current?.getCardDetails(element);
if (cardData) {
kanbanRef.current?.deleteCard(cardData);
}
});
};Parameters:
cardData: Card ID (string/number) or card object(s)
Returns: void
Use when:
- User clicks delete button on a card
- Archiving completed tasks
- Bulk removal of cards
Column Management Methods
addColumn()
Signature: addColumn(columnOptions: ColumnsModel, index: number): void
Dynamically adds a new column to the Kanban board.
import { useRef } from 'react';
import { KanbanComponent, ColumnsModel } from '@syncfusion/ej2-react-kanban';
const kanbanRef = useRef<KanbanComponent>(null);
const handleAddColumn = (): void => {
const newColumn: ColumnsModel = {
headerText: 'Blocked',
keyField: 'Blocked',
maxCount: 5,
showItemCount: true
};
// Add at position 2 (0-indexed)
kanbanRef.current?.addColumn(newColumn, 2);
};
// Add column at end
const handleAddAtEnd = (newColumn: ColumnsModel): void => {
kanbanRef.current?.addColumn(newColumn, 999);
};Parameters:
columnOptions(required): Column configuration objectindex(required): Position to insert column
Returns: void
ColumnsModel properties:
{
headerText?: string;
keyField: string | number; // Required
template?: Function;
allowToggle?: boolean;
isExpanded?: boolean;
minCount?: number;
maxCount?: number;
showAddButton?: boolean;
showItemCount?: boolean;
allowDrag?: boolean;
allowDrop?: boolean;
transitionColumns?: string[];
}deleteColumn()
Signature: deleteColumn(index: number): void
Removes a column from the Kanban board.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
const kanbanRef = useRef<KanbanComponent>(null);
const handleDeleteColumn = (columnIndex: number): void => {
kanbanRef.current?.deleteColumn(columnIndex);
};
// Delete first column
handleDeleteColumn(0);
// Delete last column (if 4 columns total)
handleDeleteColumn(3);Parameters:
index(required): Position of column to delete (0-indexed)
Returns: void
Warning: Cards in deleted column are preserved in data but become invisible. Consider using hideColumn() for temporary hiding.
hideColumn()
Signature: hideColumn(key: string | number): void
Hides a column without deleting it or its cards.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
const kanbanRef = useRef<KanbanComponent>(null);
const handleHideColumn = (): void => {
// Hide by keyField value
kanbanRef.current?.hideColumn('OnHold');
};Parameters:
key: Column's keyField value
Returns: void
Use when:
- User unchecks column visibility in settings
- Temporarily filtering out certain statuses
- Keeping data but hiding from view
showColumn()
Signature: showColumn(key: string | number): void
Unhides a previously hidden column.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
const kanbanRef = useRef<KanbanComponent>(null);
const handleShowColumn = (): void => {
kanbanRef.current?.showColumn('OnHold');
};Parameters:
key: Column's keyField value
Returns: void
Data Access Methods
getSelectedCards()
Signature: getSelectedCards(): HTMLElement[]
Returns array of DOM elements of currently selected cards.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
// Card data shape: { [key: string]: any }
const kanbanRef = useRef<KanbanComponent>(null);
const handleGetSelected = (): void => {
const selectedElements = kanbanRef.current?.getSelectedCards() || [];
selectedElements.forEach((element: HTMLElement) => {
const cardData = kanbanRef.current?.getCardDetail(element);
console.log('Selected:', cardData);
});
};
// Count selected cards
const selectedCount = (kanbanRef.current?.getSelectedCards() || []).length;Parameters: None
Returns: HTMLElement[]
Use when:
- Implementing bulk actions
- Showing count of selected items
- Preparing batch operations
getCardDetails()
Signature: getCardDetails(target: Element): Record
Gets card data object from a DOM element.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
interface KanbanCard {
Id: string;
Status: string;
[key: string]: any;
}
const kanbanRef = useRef<KanbanComponent>(null);
const handleCardClick = (element: HTMLElement): void => {
const cardData = kanbanRef.current?.getCardDetail(element) as any;
console.log('Card ID:', cardData.Id);
console.log('Status:', cardData.Status);
};
// Get details of selected card
const selected = kanbanRef.current?.getSelectedCards() || [];
if (selected.length > 0) {
const data = kanbanRef.current?.getCardDetail(selected[0]) as any;
}Parameters:
target: DOM element of the card (usually from event)
Returns: Record - Original card data object
getColumnData()
Signature: getColumnData(columnKey: string | number, dataSource?: Record[]): Record[]
Returns all cards in a specific column.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
interface KanbanCard {
Id: string;
Status: string;
[key: string]: any;
}
const kanbanRef = useRef<KanbanComponent>(null);
const handleGetColumnCards = (): void => {
// Get all cards in 'InProgress' column
const inProgressCards = kanbanRef.current?.getColumnData('InProgress') || [];
console.log('In Progress cards:', inProgressCards);
console.log('Count:', inProgressCards.length);
};
// Use with custom data source
const customData: any[] = [...];
const customColumnCards = kanbanRef.current?.getColumnData('Open', customData) || [];Parameters:
columnKey(required): Column's keyField valuedataSource(optional): Custom array (uses component's data if not provided)
Returns: Record[] - Array of cards in that column
Use when:
- Calculating column metrics
- Filtering cards by status
- Preparing data for export
getSwimlaneData()
Signature: getSwimlaneData(keyField: string): Record[]
Returns all cards in a specific swimlane.
const handleGetSwimlaneCards = () => {
// Get all cards assigned to Sarah
const sarahCards = kanbanRef.current.getSwimlaneData('Sarah');
console.log("Sarah's cards:", sarahCards);
console.log("Workload:", sarahCards.length);
};
// Calculate total effort in swimlane
const swimlaneCards = kanbanRef.current.getSwimlaneData('John');
const totalEffort = swimlaneCards.reduce((sum, card) =>
sum + (card.EstimateHours || 0), 0
);
console.log('Total effort:', totalEffort, 'hours');Parameters:
keyField(required): Swimlane key value
Returns: Record[] - Array of cards in swimlane
UI Control Methods
showSpinner()
Signature: showSpinner(): void
Displays a loading spinner on the Kanban board.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
const kanbanRef = useRef<KanbanComponent>(null);
const handleSaveToServer = async (): Promise<void> => {
kanbanRef.current?.showSpinner();
try {
await fetch('/api/save', { method: 'POST', body: JSON.stringify(data) });
} finally {
kanbanRef.current?.hideSpinner();
}
};Parameters: None
Returns: void
Use when:
- Loading data from server
- Saving bulk changes
- Long-running operations
hideSpinner()
Signature: hideSpinner(): void
Hides the loading spinner.
import { useRef } from 'react';
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
const kanbanRef = useRef<KanbanComponent>(null);
// See showSpinner() example above
kanbanRef.current?.hideSpinner();Parameters: None
Returns: void
refreshUI()
Signature: refreshUI(args: ActionEventArgs, index?: number): void
Refreshes the Kanban UI after making data changes programmatically.
const handleRefreshUI = () => {
const updates = {
addedRecords: [newCard1, newCard2],
changedRecords: [updatedCard],
deletedRecords: [deletedCard]
};
kanbanRef.current.refreshUI(updates);
};Parameters:
args: ActionEventArgs object with added/changed/deleted recordsindex(optional): Specific card index to refresh
Returns: void
refreshHeader()
Signature: refreshHeader(): void
Refreshes only the column headers (useful after changing header templates).
const handleUpdateHeaders = () => {
kanbanRef.current.refreshHeader();
};Parameters: None
Returns: void
destroy()
Signature: destroy(): void
Completely removes the Kanban component from the DOM and detaches all event handlers.
useEffect(() => {
return () => {
// Cleanup on unmount
kanbanRef.current.destroy();
};
}, []);Parameters: None
Returns: void
Use when:
- Component unmounts
- Recycling DOM for large lists
Dialog Methods
openDialog()
Signature: openDialog(action: 'Add' | 'Edit', data?: Record): void
Manually opens the card creation or editing dialog.
const handleOpenAddDialog = () => {
kanbanRef.current.openDialog('Add');
};
const handleOpenEditDialog = (cardData) => {
kanbanRef.current.openDialog('Edit', cardData);
};
// From button click
const handleEditButton = (element) => {
const cardData = kanbanRef.current.getCardDetails(element);
kanbanRef.current.openDialog('Edit', cardData);
};Parameters:
action(required): 'Add' or 'Edit'data(optional): Card data to edit
Returns: void
closeDialog()
Signature: closeDialog(): void
Manually closes the dialog.
const handleCloseDialog = () => {
kanbanRef.current.closeDialog();
};Parameters: None
Returns: void
Common Patterns
Pattern 1: Build Custom Card Duplication
const handleDuplicateCard = (cardElement) => {
const original = kanbanRef.current.getCardDetails(cardElement);
const duplicate = {
...original,
Id: `${original.Id}-copy`,
Summary: `${original.Summary} (Copy)`
};
kanbanRef.current.addCard(duplicate);
};Pattern 2: Bulk Update on Drag
const handleBulkStatusUpdate = (status) => {
const selected = kanbanRef.current.getSelectedCards();
selected.forEach(element => {
const card = kanbanRef.current.getCardDetails(element);
card.Status = status;
kanbanRef.current.updateCard(card);
});
};Pattern 3: Archive Old Cards
const handleArchiveOldCards = () => {
const allCards = kanbanRef.current.getColumnData('Done');
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - 30);
const oldCards = allCards.filter(card =>
new Date(card.CompletedDate) < cutoffDate
);
oldCards.forEach(card => kanbanRef.current.deleteCard(card.Id));
console.log(`Archived ${oldCards.length} cards`);
};Pattern 4: Reorder Cards in Column
const handlePrioritizeCard = (cardElement) => {
const card = kanbanRef.current.getCardDetails(cardElement);
kanbanRef.current.deleteCard(card.Id);
kanbanRef.current.addCard(card, 0); // Move to top
};Pattern 5: Export Column Data
const handleExportColumn = (columnKey) => {
const cards = kanbanRef.current.getColumnData(columnKey);
const csv = cards.map(c => `${c.Id},${c.Summary},${c.Status}`).join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${columnKey}-export.csv`;
a.click();
};Kanban Properties: Configuration and Settings
Table of Contents
- Overview
- Core Properties
- Card Configuration
- Column Configuration
- Swimlane Configuration
- Dialog Configuration
- Advanced Properties
- Complete Configuration Example
Overview
Kanban properties control how the board behaves, appears, and processes data. Properties are set via component attributes and configure everything from drag-drop behavior to WIP limits.
Core Properties
allowDragAndDrop
Type: boolean Default: true
Enables or disables drag-and-drop functionality globally across the Kanban board.
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
<KanbanComponent
allowDragAndDrop={true} // Enable drag-drop
keyField="Status"
dataSource={data}
>
{/* ... */}
</KanbanComponent>Use when:
- You want to prevent users from moving cards
- You need read-only boards
- You want to lock cards until a condition is met
Related: allowDrag and allowDrop on individual columns provide finer control
keyField
Type: string Default: None (REQUIRED)
Specifies the field in your data source that determines column assignment.
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
const data = [
{ Id: 1, Status: 'Open', Summary: 'Task A' }, // Status = 'Open' → Open column
{ Id: 2, Status: 'InProgress', Summary: 'Task B' } // Status = 'InProgress' → In Progress
];
<KanbanComponent keyField="Status" dataSource={data}>
{/* ... */}
</KanbanComponent>Critical: Must match a field name in your data objects.
dataSource
Type: DataManager | Record[] Default: []
Provides data to populate the Kanban board. Can be a static array or DataManager for remote data.
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
import { DataManager, ODataAdaptor } from '@syncfusion/ej2-data';
// Static array
<KanbanComponent dataSource={[
{ Id: 1, Status: 'Open', Summary: 'Task' }
]}>
{/* ... */}
</KanbanComponent>
// Dynamic with DataManager
const dataManager: DataManager = new DataManager({
url: 'https://api.example.com/tasks',
adaptor: new ODataAdaptor()
});
<KanbanComponent dataSource={dataManager}>
{/* ... */}
</KanbanComponent>allowSorting
Type: boolean Default: false
Allows user to click column headers to sort cards.
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
<KanbanComponent allowSorting={true} sortSettings={{ field: 'Priority' }}>
{/* Column headers become clickable to sort */}
</KanbanComponent>allowSelection
Type: boolean Default: true
Enables card selection functionality (single or multiple).
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
<KanbanComponent
allowSelection={true}
cardSettings={{ selectionType: 'Multiple' }}
>
{/* Users can select cards */}
</KanbanComponent>allowKeyboard
Type: boolean Default: true
Enables keyboard navigation (arrow keys, Tab, Enter, Space).
import { KanbanComponent } from '@syncfusion/ej2-react-kanban';
<KanbanComponent allowKeyboard={true}>
{/* Users can navigate with keyboard */}
</KanbanComponent>Card Configuration
cardSettings
Type: CardSettingsModel
Configures how cards are displayed, including header, content, templates, and behavior.
import { CardSettingsModel } from '@syncfusion/ej2-react-kanban';
<KanbanComponent
cardSettings={{
headerField: 'Id', // Field for card header/ID
contentField: 'Summary', // Field for main content
showHeader: true, // Display card header
selectionType: 'Single', // Single or Multiple selection
tagsField: 'Tags', // Field for tags display
grabberField: 'Priority', // Field for left-side indicator
footerCssField: 'Status' // Field for footer styling
}}
>
{/* ... */}
</KanbanComponent>Key Sub-properties:
| Property | Type | Purpose |
|---|---|---|
headerField | string | Unique card identifier (required) |
contentField | string | Main card text display |
template | Function | Custom card JSX renderer |
showHeader | boolean | Toggle header visibility |
selectionType | 'Single'\ | 'Multiple' |
tagsField | string | Display comma-separated tags |
grabberField | string | Visual left-edge indicator field |
footerCssField | string | Footer CSS class field |
allowPrint
Type: boolean Default: true
Allows printing the Kanban board.
<KanbanComponent allowPrint={true}>
{/* Users can print the board */}
</KanbanComponent>Column Configuration
columns
Type: ColumnsModel[] Default: []
Defines all columns in the Kanban board with their properties.
import { ColumnsModel } from '@syncfusion/ej2-react-kanban';
const columns: ColumnsModel[] = [
{
headerText: 'To Do',
keyField: 'Open',
showAddButton: true,
showItemCount: true,
maxCount: 10,
minCount: 1
},
{
headerText: 'In Progress',
keyField: 'InProgress',
allowDrag: true,
allowDrop: true,
maxCount: 5
},
{
headerText: 'Done',
keyField: 'Close',
allowDrag: false
}
];
<KanbanComponent columns={columns}>
{/* ... */}
</KanbanComponent>Column Sub-properties:
| Property | Type | Description |
|---|---|---|
headerText | string | Column display name |
keyField | string\ | number |
allowDrag | boolean | Allow dragging FROM this column |
allowDrop | boolean | Allow dropping INTO this column |
allowToggle | boolean | Allow expand/collapse |
isExpanded | boolean | Initial expanded state (default: true) |
minCount | number | Minimum cards required (WIP) |
maxCount | number | Maximum cards allowed (WIP) |
showAddButton | boolean | Show + button |
showItemCount | boolean | Show card count badge |
template | Function | Custom column header JSX |
transitionColumns | string[] | Allowed target columns |
Swimlane Configuration
swimlaneSettings
Type: SwimlaneSettingsModel
Configures swimlane grouping to organize cards horizontally by categories.
import { SwimlaneSettingsModel } from '@syncfusion/ej2-react-kanban';
const swimlaneSettings: SwimlaneSettingsModel = {
keyField: 'Assignee', // Group by this field
textField: 'AssigneeName', // Display this field
template: customTemplate, // Custom swimlane header
allowDragAndDrop: true, // Drag across swimlanes
showEmptyRow: true, // Show empty swimlanes
showItemCount: true // Show swimlane card count
};
<KanbanComponent swimlaneSettings={swimlaneSettings}>
{/* ... */}
</KanbanComponent>Sub-properties:
| Property | Type | Purpose |
|---|---|---|
keyField | string | Field to group by (required) |
textField | string | Display name field (optional) |
template | Function | Custom swimlane header JSX |
allowDragAndDrop | boolean | Allow cross-swimlane drag |
showEmptyRow | boolean | Display empty swimlanes |
showItemCount | boolean | Show card counts |
Dialog Configuration
dialogSettings
Type: DialogSettingsModel
Configures the dialog for adding and editing cards.
import { DialogSettingsModel, DialogFieldsModel } from '@syncfusion/ej2-react-kanban';
const dialogSettings: DialogSettingsModel = {
fields: [
{ text: 'Task ID', key: 'Id', type: 'TextBox' } as DialogFieldsModel,
{ text: 'Summary', key: 'Summary', type: 'TextArea' } as DialogFieldsModel,
{ text: 'Status', key: 'Status', type: 'DropDown' } as DialogFieldsModel,
{ text: 'Priority', key: 'Priority', type: 'Numeric' } as DialogFieldsModel,
{ text: 'Assignee', key: 'Assignee', type: 'DropDown' } as DialogFieldsModel
],
template: customDialogTemplate // Custom dialog JSX
};
<KanbanComponent dialogSettings={dialogSettings}>
{/* ... */}
</KanbanComponent>Field Type Options:
TextBox- Single-line text inputTextArea- Multi-line textNumeric- Number inputDropDown- Select list
Advanced Properties
sortSettings
Type: SortSettingsModel
Defines default card sorting behavior.
import { SortSettingsModel } from '@syncfusion/ej2-react-kanban';
const sortSettings: SortSettingsModel = {
field: 'Priority',
direction: 'Descending'
};
<KanbanComponent sortSettings={sortSettings}>
{/* Cards sorted by Priority (High → Low) */}
</KanbanComponent>constraintType
Type: 'Column' | 'Swimlane' Default: 'Column'
Specifies whether min/max constraints apply to columns or swimlanes.
type ConstraintType = 'Column' | 'Swimlane';
<KanbanComponent constraintType="Column">
{/* WIP limits apply per column */}
</KanbanComponent>
<KanbanComponent constraintType="Swimlane">
{/* WIP limits apply per swimlane */}
</KanbanComponent>locale
Type: string Default: 'en-US'
Sets the language for Kanban UI text.
<KanbanComponent locale="es">
{/* Spanish localization */}
</KanbanComponent>Supported locales:
en- Englishes- Spanishfr- Frenchde- Germanja- Japanesezh- Chinese
enableVirtualization
Type: boolean Default: false
Enables virtual rendering for large datasets (renders only visible cards).
<KanbanComponent enableVirtualization={true}>
{/* Efficiently handles 1000+ cards */}
</KanbanComponent>height
Type: string | number Default: 'auto'
Sets the Kanban board height.
<KanbanComponent height="600px">
{/* Fixed height of 600px */}
</KanbanComponent>
<KanbanComponent height="100%">
{/* Full parent height */}
</KanbanComponent>width
Type: string | number Default: '100%'
Sets the Kanban board width.
<KanbanComponent width="1200px">
{/* Fixed width */}
</KanbanComponent>Sorting, Filtering, and Work-in-Progress (WIP) Validation
Table of Contents
- Overview
- Sorting Cards
- WIP Limits (Min/Max Card Count)
- Card Selection
- Filtering and Search
- Validation Patterns
- Troubleshooting
Overview
Kanban supports sorting cards by a single field through the sortSettings property. For multi-field sorting, you must pre-sort the data before passing it to the component. Additionally, you can enforce work-in-progress (WIP) limits to prevent overload, enable selection of multiple cards for batch operations, and implement filtering to focus on specific tasks.
Sorting Cards
Basic Sorting
Control card order with sortSettings:
import { SortSettingsModel } from '@syncfusion/ej2-react-kanban';
const sortSettings: SortSettingsModel = {
field: 'Priority', // Sort by Priority field
direction: 'Descending' // High priority first
};
<KanbanComponent sortSettings={sortSettings}>
{/* ... */}
</KanbanComponent>Multi-Field Sorting
Note: The SortSettingsModel currently supports sorting by a single field only. To achieve multi-field sorting, you must manually sort the data in your component before passing it to the Kanban component:
import { SortSettingsModel } from '@syncfusion/ej2-react-kanban';
// Sort data by Priority first, then by DueDate
const sortedTasks = [...tasks].sort((a, b) => {
// First sort by Priority (Descending)
if (a.Priority !== b.Priority) {
const priorityOrder = { 'High': 1, 'Medium': 2, 'Low': 3 };
return (priorityOrder[a.Priority] || 999) - (priorityOrder[b.Priority] || 999);
}
// Then sort by DueDate (Ascending)
return new Date(a.DueDate).getTime() - new Date(b.DueDate).getTime();
});
const sortSettings: SortSettingsModel = {
field: 'Priority',
direction: 'Descending'
};
<KanbanComponent dataSource={sortedTasks} sortSettings={sortSettings}>
{/* ... */}
</KanbanComponent>Result: 1. Cards sorted by Priority (High → Low) 2. Within each priority, sorted by DueDate (Early → Late) 3. Kanban respects the pre-sorted data order
Enable User Sorting
Allow users to click column headers to sort:
import { SortSettingsModel } from '@syncfusion/ej2-react-kanban';
const sortSettings: SortSettingsModel = {
field: 'Id',
direction: 'Ascending'
};
<KanbanComponent
allowSorting={true}
sortSettings={sortSettings}
>
{/* Users can click headers to re-sort */}
</KanbanComponent>Sorting Strategies
By Priority:
sortSettings={{ field: 'Priority', direction: 'Descending' }}High-priority tasks float up.
By Due Date:
sortSettings={{ field: 'DueDate', direction: 'Ascending' }}Soonest deadlines first.
By Estimated Effort:
sortSettings={{ field: 'EstimateHours', direction: 'Ascending' }}Quick wins first (useful for sprint planning).
By Card ID (Rank):
sortSettings={{ field: 'RankId', direction: 'Ascending' }}Maintain manual ordering by rank.
WIP Limits (Min/Max Card Count)
Prevent columns from becoming bottlenecks with Work-in-Progress limits:
Max Limit (Column Overload Prevention)
<ColumnDirective
keyField="InProgress"
headerText="In Progress"
maxCount={5} // Max 5 cards at a time
/>Behavior:
- Kanban allows dragging up to 5 cards into column
- If attempting to exceed limit, shows warning or prevents drop
- Helps prevent team overcommitment
Min Limit (Bottleneck Detection)
<ColumnDirective
keyField="InProgress"
headerText="In Progress"
minCount={1} // At least 1 card should be in progress
/>Behavior:
- Warns if column has fewer cards than minimum
- Signals understaffed or idle column
- Helps balance work across team
Combined Min and Max
<ColumnDirective
keyField="InProgress"
headerText="In Progress"
minCount={2} // At least 2 tasks
maxCount={8} // No more than 8 tasks
/>Sweet spot: 2-8 tasks keeps team focused and balanced.
Visual Indicators
Access count info via custom templates:
const columnHeaderTemplate = (props) => {
const count = props.cardCount || 0;
const max = props.maxCount;
const isWarning = max && count >= max * 0.8; // 80% of max
const isExceeded = max && count > max;
return (
<div style={{
backgroundColor: isExceeded ? '#ffebee' : isWarning ? '#fff3e0' : 'transparent',
padding: '8px',
borderRadius: '4px'
}}>
<strong>{props.headerText}</strong>
<div style={{ fontSize: '12px', marginTop: '4px' }}>
{count}{max ? `/${max}` : ''} items
</div>
{isExceeded && (
<div style={{ color: 'red', fontSize: '12px', fontWeight: 'bold' }}>
Over limit!
</div>
)}
</div>
);
};
<ColumnDirective
keyField="InProgress"
template={columnHeaderTemplate}
/>Card Selection
Enable users to select multiple cards for bulk operations:
Single Selection (Default)
<KanbanComponent
cardSettings={{
selectionType: 'Single'
}}
>
{/* Users can select one card at a time */}
</KanbanComponent>Multiple Selection
<KanbanComponent
cardSettings={{
selectionType: 'Multiple'
}}
cardSelectionChanging={(args) => {
console.log('Selected cards:', args.selectedCards);
}}
>
{/* Users can Ctrl+Click or Shift+Click to select multiple */}
</KanbanComponent>Bulk Operations on Selected Cards
const handleBulkAssign = () => {
const selectedCards = kanbanRef.current.getSelectedCards();
selectedCards.forEach(card => {
card.Assignee = 'Sarah';
kanbanRef.current.updateCard(card);
});
};
<button onClick={handleBulkAssign}>Assign to Sarah</button>Filtering and Search
Card-Level Filtering
const handleSearch = (searchText) => {
const filtered = data.filter(card =>
card.Summary.toLowerCase().includes(searchText.toLowerCase()) ||
card.Id.toString().includes(searchText)
);
setData(filtered);
};
<input
type="text"
placeholder="Search tasks..."
onChange={(e) => handleSearch(e.target.value)}
style={{ marginBottom: '20px', padding: '8px', width: '300px' }}
/>
<KanbanComponent dataSource={data}>
{/* Filtered data shows */}
</KanbanComponent>Status-Based Filtering
Show specific columns or hide:
const [showOnlyActive, setShowOnlyActive] = useState(false);
const getVisibleColumns = () => {
if (showOnlyActive) {
return ['Open', 'InProgress']; // Only show active work
}
return ['Open', 'InProgress', 'Closed'];
};
<KanbanComponent>
<ColumnsDirective>
{getVisibleColumns().map(status => (
<ColumnDirective key={status} keyField={status} headerText={status} />
))}
</ColumnsDirective>
</KanbanComponent>
<label>
<input
type="checkbox"
checked={showOnlyActive}
onChange={(e) => setShowOnlyActive(e.target.checked)}
/>
Show Active Work Only
</label>Priority-Based Display
Highlight or filter by priority:
const handleFilterByPriority = (priority) => {
const filtered = data.filter(card => card.Priority === priority);
setData(filtered);
};
<div style={{ marginBottom: '20px' }}>
<button onClick={() => handleFilterByPriority('Critical')}>Show Critical</button>
<button onClick={() => handleFilterByPriority('High')}>Show High</button>
<button onClick={() => setData(initialData)}>Show All</button>
</div>
<KanbanComponent dataSource={data}>
{/* ... */}
</KanbanComponent>Assignee Filter
const [selectedAssignee, setSelectedAssignee] = useState(null);
const filtered = selectedAssignee
? data.filter(card => card.Assignee === selectedAssignee)
: data;
const assignees = [...new Set(data.map(d => d.Assignee))];
<div style={{ marginBottom: '20px' }}>
<select value={selectedAssignee || ''} onChange={(e) => setSelectedAssignee(e.target.value || null)}>
<option value="">All Assignees</option>
{assignees.map(name => <option key={name} value={name}>{name}</option>)}
</select>
</div>
<KanbanComponent dataSource={filtered}>
{/* ... */}
</KanbanComponent>Validation Patterns
Prevent Moving to Done Without Completion
const handleDragStop = (args) => {
if (args.data.Status === 'Closed' && args.data.CompletionPercentage < 100) {
args.cancel = true;
alert('Task must be 100% complete before closing');
}
};
<KanbanComponent dragStop={handleDragStop}>
{/* ... */}
</KanbanComponent>Enforce Status Workflow
const validTransitions = {
'Open': ['InProgress'],
'InProgress': ['Testing', 'Open'],
'Testing': ['Closed', 'InProgress'],
'Closed': []
};
<ColumnDirective
keyField="Open"
transitionColumns={validTransitions['Open']}
/>
<ColumnDirective
keyField="InProgress"
transitionColumns={validTransitions['InProgress']}
/>Troubleshooting
Sorting not working
- Ensure field name matches data property exactly
- Verify sort direction is 'Ascending' or 'Descending' (case-sensitive)
- Check that field values are consistent type (not mixed strings/numbers)
- Remember: only single-field sorting is supported via
sortSettings - For multi-field sorting, pre-sort your data array before passing to
dataSource
WIP limits not enforcing
- Verify
maxCount/minCountare set onColumnDirective, notKanbanComponent - Check that drag events aren't being cancelled elsewhere
- Monitor console for validation errors
Selection not working
- Confirm
selectionType: 'Multiple'is set - Verify
cardSelectionChangingevent is properly bound - Check that cards are not disabled or read-only
Filter not showing results
- Ensure filter logic matches actual data values
- Check for case sensitivity (use
.toLowerCase()) - Verify filtered data array still passed to
dataSource