Kanban is a board component for visualizing workflow and managing tasks with drag-and-drop, including touch drag-and-drop on mobile devices. Based on jkanban by Riktar.
A board with three columns bound to a list of KanbanColumn. Drag items between columns and add new ones with addItemButton; drop and itemAdd fire ajax events.
Any key-value data added to a KanbanItem via putData(key, value) is rendered as a data-* attribute on the card. Here the extender turns them into badges and CSS attribute selectors color the priority; inspect a card to see the attributes.
Control the spacing between boards (gutter), the width of each board (widthBoard) and whether boards use percentage-based widths (responsivePercentage).
<style>
.kanban-item-title {
font-weight: 600;
}
.kanban-item-description {
font-size: 0.8rem;
opacity: 0.7;
margin-top: 2px;
}
#formBasicUsage .kanban-board header.kanban-todo { background-color: #4f46e5; color: #fff; }
#formBasicUsage .kanban-board header.kanban-inprogress { background-color: #ea580c; color: #fff; }
#formBasicUsage .kanban-board header.kanban-done { background-color: #16a34a; color: #fff; }
#formCustomData .kanban-board header.kanban-todo { background: #6366f1; color: #fff; }
#formCustomData .kanban-board header.kanban-inprogress { background: #f59e0b; color: #fff; }
#formCustomData .kanban-board header.kanban-done { background: #10b981; color: #fff; }
.kanban-item[data-priority="high"] { border-left: 4px solid #ef4444; }
.kanban-item[data-priority="medium"] { border-left: 4px solid #f59e0b; }
.kanban-item[data-priority="low"] { border-left: 4px solid #10b981; }
.kanban-badges { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.kanban-badge { font-size: 0.65rem; padding: 1px 6px; border-radius: 3px; font-weight: 600; }
.kb-priority-high { background: #fef2f2; color: #dc2626; }
.kb-priority-medium { background: #fffbeb; color: #d97706; }
.kb-priority-low { background: #f0fdf4; color: #16a34a; }
.kb-assignee { background: #eef2ff; color: #4f46e5; }
.kb-duedate { background: #fef3c7; color: #92400e; }
.kb-points { background: #d1fae5; color: #065f46; }
.kb-label { background: #f3e8ff; color: #7c3aed; }
#formLayoutTuning .kanban-board header.kanban-backlog { background-color: #6b7280; color: #fff; }
#formLayoutTuning .kanban-board header.kanban-design { background-color: #8b5cf6; color: #fff; }
#formLayoutTuning .kanban-board header.kanban-development { background-color: #f59e0b; color: #fff; }
#formLayoutTuning .kanban-board header.kanban-testing { background-color: #10b981; color: #fff; }
</style>
<div class="card">
<h5>Basic Usage</h5>
<p>A board with three columns bound to a list of <code>KanbanColumn</code>. Drag items between columns and add new ones with <code>addItemButton</code>; <i>drop</i> and <i>itemAdd</i> fire ajax events.</p>
<h:form id="formBasicUsage">
<p:messages id="messages" showSummary="true" showDetail="true"/>
<pe:kanban id="kanban"
widgetVar="kanbanBasicWidget"
value="#{kanbanController.columns}"
draggable="true"
addItemButton="true"
style="height:400px">
<p:ajax event="drop" listener="#{kanbanController.onDrop}" update="messages" />
<p:ajax event="itemAdd" listener="#{kanbanController.onAddItem}" update="kanban messages" />
</pe:kanban>
</h:form>
</div>
<div class="card">
<h5>Custom Data Attributes</h5>
<p>Any key-value data added to a <code>KanbanItem</code> via <code>putData(key, value)</code> is rendered as a <code>data-*</code> attribute on the card. Here the <code>extender</code> turns them into badges and CSS attribute selectors color the priority; inspect a card to see the attributes.</p>
<h:form id="formCustomData">
<pe:kanban id="kanbanCustomData"
value="#{kanbanCustomDataController.columns}"
draggable="true"
extender="kanbanExtender"
style="height:400px" />
</h:form>
<h:outputScript>
//<![CDATA[
function kanbanExtender() {
this.boards.forEach(function(board) {
(board.item || []).forEach(function(item) {
var badges = [];
if (item.priority) badges.push(
'<span class="kanban-badge kb-priority-' + item.priority + '">' + item.priority + '</span>');
if (item.assignee) badges.push(
'<span class="kanban-badge kb-assignee">' + item.assignee + '</span>');
if (item.dueDate) badges.push(
'<span class="kanban-badge kb-duedate">' + item.dueDate + '</span>');
if (item.storyPoints) badges.push(
'<span class="kanban-badge kb-points">' + item.storyPoints + 'pts</span>');
if (item.labels) {
item.labels.split(',').forEach(function(l) {
badges.push('<span class="kanban-badge kb-label">' + l.trim() + '</span>');
});
}
if (badges.length) {
item.title += '<div class="kanban-badges">' + badges.join('') + '</div>';
}
});
});
}
//]]>
</h:outputScript>
</div>
<div class="card">
<h5>Layout Tuning</h5>
<p>Control the spacing between boards (<code>gutter</code>), the width of each board (<code>widthBoard</code>) and whether boards use percentage-based widths (<code>responsivePercentage</code>).</p>
<h:form id="formLayoutTuning">
<h:panelGrid columns="3" styleClass="p-fluid" columnClasses="p-2,p-2,p-2">
<p:outputLabel for="gutter" value="Gutter" styleClass="font-bold" />
<p:inputText id="gutter" value="#{kanbanLayoutController.gutter}" style="width:100px">
<p:ajax update="kanban" process="@this" />
</p:inputText>
<p:outputLabel value="Spacing between boards (e.g. 5px, 15px, 30px)" />
<p:outputLabel for="widthBoard" value="Board Width" styleClass="font-bold" />
<p:inputText id="widthBoard" value="#{kanbanLayoutController.widthBoard}" style="width:100px">
<p:ajax update="kanban" process="@this" />
</p:inputText>
<p:outputLabel value="Width of each board (e.g. 200px, 250px, 350px)" />
<p:outputLabel for="responsivePercentage" value="Responsive %" styleClass="font-bold" />
<p:selectOneMenu id="responsivePercentage" value="#{kanbanLayoutController.responsivePercentage}">
<f:selectItem itemLabel="false" itemValue="false" />
<f:selectItem itemLabel="true" itemValue="true" />
<p:ajax update="kanban" process="@this" />
</p:selectOneMenu>
<p:outputLabel value="Use percentage-based board widths" />
</h:panelGrid>
<pe:kanban id="kanban"
widgetVar="kanbanLayoutWidget"
value="#{kanbanLayoutController.columns}"
draggable="true"
gutter="#{kanbanLayoutController.gutter}"
widthBoard="#{kanbanLayoutController.widthBoard}"
responsivePercentage="#{kanbanLayoutController.responsivePercentage}"
style="height:400px;margin-top:12px">
</pe:kanban>
</h:form>
</div>
package org.primefaces.extensions.showcase.controller;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.event.KanbanAddEvent;
import org.primefaces.extensions.event.KanbanDragEvent;
import org.primefaces.extensions.event.KanbanItemClickEvent;
import org.primefaces.extensions.model.kanban.KanbanColumn;
import org.primefaces.extensions.model.kanban.KanbanItem;
/**
* KanbanController
*
* @author @jxmai
* @since 16.0.0
*/
@Named
@ViewScoped
public class KanbanController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<KanbanColumn> columns;
private KanbanItem selectedItem;
private String selectedColumnId;
@PostConstruct
public void init() {
columns = new ArrayList<>();
// To Do column
KanbanColumn todoColumn = new KanbanColumn("todo", "To Do");
todoColumn.setCssClass("kanban-todo");
todoColumn.addItem(new KanbanItem("item1", "Add support for Kanban",
"Create a new Kanban component for PrimeFaces Extensions"));
todoColumn.addItem(new KanbanItem("item2", "Write documentation",
"Document the new component"));
// In Progress column
KanbanColumn inProgressColumn = new KanbanColumn("inprogress", "In Progress");
inProgressColumn.setCssClass("kanban-inprogress");
inProgressColumn.addItem(new KanbanItem("item3", "Design component",
"Design the Kanban board component"));
// Done column
KanbanColumn doneColumn = new KanbanColumn("done", "Done");
doneColumn.setCssClass("kanban-done");
doneColumn.addItem(new KanbanItem("item4", "Research libraries",
"Research available Kanban libraries"));
columns.add(todoColumn);
columns.add(inProgressColumn);
columns.add(doneColumn);
}
public void onDrop(KanbanDragEvent event) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Moved",
"Item " + event.getItemId() + " moved from " + event.getSourceColumnId()
+ " to " + event.getTargetColumnId() + " at position " + event.getNewPosition());
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void onAddItem(KanbanAddEvent event) {
for (KanbanColumn column : columns) {
if (column.getId().equals(event.getColumnId())) {
int count = column.getItems().size() + 1;
column.addItem(new KanbanItem("new-" + count, "New Task " + count,
"Click to edit this task"));
break;
}
}
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Added",
"New item added to column " + event.getColumnId());
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void onItemClick(KanbanItemClickEvent event) {
String itemId = event.getItemId();
String columnId = event.getColumnId();
for (KanbanColumn col : columns) {
if (col.getId().equals(columnId)) {
for (KanbanItem item : col.getItems()) {
if (item.getId().equals(itemId)) {
this.selectedItem = item;
this.selectedColumnId = columnId;
break;
}
}
}
}
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Clicked",
"Loaded task: " + (selectedItem != null ? selectedItem.getTitle() : itemId));
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void saveItem() {
if (selectedItem != null) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Updated",
"Updated task: " + selectedItem.getTitle());
FacesContext.getCurrentInstance().addMessage(null, message);
}
}
public List<KanbanColumn> getColumns() {
return columns;
}
public void setColumns(List<KanbanColumn> columns) {
this.columns = columns;
}
public KanbanItem getSelectedItem() {
return selectedItem;
}
public void setSelectedItem(KanbanItem selectedItem) {
this.selectedItem = selectedItem;
}
public String getSelectedColumnId() {
return selectedColumnId;
}
public void setSelectedColumnId(String selectedColumnId) {
this.selectedColumnId = selectedColumnId;
}
}
package org.primefaces.extensions.showcase.controller.kanban;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.model.kanban.KanbanColumn;
import org.primefaces.extensions.model.kanban.KanbanItem;
@Named
@ViewScoped
public class KanbanCustomDataController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<KanbanColumn> columns;
@PostConstruct
public void init() {
columns = new ArrayList<>();
KanbanColumn todo = new KanbanColumn();
todo.setId("todo");
todo.setTitle("To Do");
todo.setCssClass("kanban-todo");
todo.getItems().addAll(List.of(
new KanbanItem("td-1", "Design new landing page")
.putData("priority", "high")
.putData("assignee", "Alice")
.putData("dueDate", "2026-06-10")
.putData("labels", "design,frontend")
.putData("storyPoints", 5),
new KanbanItem("td-2", "Research competitor features")
.putData("priority", "medium")
.putData("assignee", "Bob")
.putData("dueDate", "2026-06-15")
.putData("labels", "research")
.putData("storyPoints", 3),
new KanbanItem("td-3", "Write unit tests")
.putData("priority", "low")
.putData("assignee", "Carol")
.putData("dueDate", "2026-06-20")
.putData("labels", "testing")
.putData("storyPoints", 8)));
columns.add(todo);
KanbanColumn inprogress = new KanbanColumn();
inprogress.setId("inprogress");
inprogress.setTitle("In Progress");
inprogress.setCssClass("kanban-inprogress");
inprogress.getItems().addAll(List.of(
new KanbanItem("ip-1", "Implement OAuth integration")
.putData("priority", "high")
.putData("assignee", "Alice")
.putData("dueDate", "2026-06-05")
.putData("labels", "backend,security")
.putData("storyPoints", 13),
new KanbanItem("ip-2", "API rate limiting")
.putData("priority", "high")
.putData("assignee", "Bob")
.putData("dueDate", "2026-06-08")
.putData("labels", "backend,performance")
.putData("storyPoints", 5)));
columns.add(inprogress);
KanbanColumn done = new KanbanColumn();
done.setId("done");
done.setTitle("Done");
done.setCssClass("kanban-done");
done.getItems().addAll(List.of(
new KanbanItem("dn-1", "Setup CI/CD pipeline")
.putData("priority", "high")
.putData("assignee", "Carol")
.putData("dueDate", "2026-05-28")
.putData("labels", "devops")
.putData("storyPoints", 8),
new KanbanItem("dn-2", "Database migration script")
.putData("priority", "medium")
.putData("assignee", "Bob")
.putData("dueDate", "2026-05-30")
.putData("labels", "backend,database")
.putData("storyPoints", 3)));
columns.add(done);
}
public List<KanbanColumn> getColumns() {
return columns;
}
}
package org.primefaces.extensions.showcase.controller;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.model.kanban.KanbanColumn;
import org.primefaces.extensions.model.kanban.KanbanItem;
/**
* KanbanLayoutController
*
* @since 16.0.0
*/
@Named
@ViewScoped
public class KanbanLayoutController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<KanbanColumn> columns;
private String gutter = "15px";
private String widthBoard = "250px";
private boolean responsivePercentage;
@PostConstruct
public void init() {
columns = new ArrayList<>();
KanbanColumn backlogColumn = new KanbanColumn("backlog", "Backlog");
backlogColumn.setCssClass("kanban-backlog");
backlogColumn.addItem(new KanbanItem("item1", "Gather requirements",
"Collect and document all project requirements"));
backlogColumn.addItem(new KanbanItem("item2", "Create wireframes",
"Design initial wireframes for the new feature"));
KanbanColumn designColumn = new KanbanColumn("design", "Design");
designColumn.setCssClass("kanban-design");
designColumn.addItem(new KanbanItem("item3", "UI mockups",
"Create high-fidelity UI mockups"));
designColumn.addItem(new KanbanItem("item4", "Review designs",
"Review designs with stakeholders"));
KanbanColumn developmentColumn = new KanbanColumn("development", "Development");
developmentColumn.setCssClass("kanban-development");
developmentColumn.addItem(new KanbanItem("item5", "Set up project",
"Initialize project and configure build tools"));
developmentColumn.addItem(new KanbanItem("item6", "Implement core",
"Implement core functionality"));
KanbanColumn testingColumn = new KanbanColumn("testing", "Testing");
testingColumn.setCssClass("kanban-testing");
testingColumn.addItem(new KanbanItem("item7", "Write unit tests",
"Write unit tests for all modules"));
testingColumn.addItem(new KanbanItem("item8", "Integration tests",
"Run and validate integration tests"));
columns.add(backlogColumn);
columns.add(designColumn);
columns.add(developmentColumn);
columns.add(testingColumn);
}
public List<KanbanColumn> getColumns() {
return columns;
}
public void setColumns(List<KanbanColumn> columns) {
this.columns = columns;
}
public String getGutter() {
return gutter;
}
public void setGutter(String gutter) {
this.gutter = gutter;
}
public String getWidthBoard() {
return widthBoard;
}
public void setWidthBoard(String widthBoard) {
this.widthBoard = widthBoard;
}
public boolean isResponsivePercentage() {
return responsivePercentage;
}
public void setResponsivePercentage(boolean responsivePercentage) {
this.responsivePercentage = responsivePercentage;
}
}