Kanban supports touch dragging, board dragging, drag handles and per-board drop restrictions. 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.
Set touchDraggable to enable touch drag-and-drop for mobile devices.
Reorder entire boards (columns) by dragging them. Use dragBoards to enable or disable it and the dragBoard / dragendBoard events to react.
With dragHandle only the handle icon of an item is draggable, which prevents accidental drags on cards with interactive content. The handle style is set via the extender.
Use the dragTo property of a column to list the columns items may be dropped into. Items follow a strict workflow (Backlog, In Progress, Review, Done); restricted columns are dimmed while dragging and disallowed drops are cancelled.
<style>
.kanban-item-title {
font-weight: 600;
}
.kanban-item-description {
font-size: 0.8rem;
opacity: 0.7;
margin-top: 2px;
}
#formTouchDrag .kanban-board header.kanban-todo,
#formDragHandle .kanban-board header.kanban-todo { background-color: #4f46e5; color: #fff; }
#formTouchDrag .kanban-board header.kanban-inprogress,
#formDragHandle .kanban-board header.kanban-inprogress { background-color: #ea580c; color: #fff; }
#formTouchDrag .kanban-board header.kanban-done,
#formDragHandle .kanban-board header.kanban-done { background-color: #16a34a; color: #fff; }
#formBoardDrag .kanban-board header.kanban-discovery { background-color: #8b5cf6; color: #fff; }
#formBoardDrag .kanban-board header.kanban-design { background-color: #f59e0b; color: #fff; }
#formBoardDrag .kanban-board header.kanban-development { background-color: #3b82f6; color: #fff; }
#formBoardDrag .kanban-board header.kanban-testing { background-color: #10b981; color: #fff; }
#formDropRestrictions .kanban-board header.kanban-backlog { background-color: #6b7280; color: #fff; }
#formDropRestrictions .kanban-board header.kanban-inprogress { background-color: #3b82f6; color: #fff; }
#formDropRestrictions .kanban-board header.kanban-review { background-color: #f59e0b; color: #fff; }
#formDropRestrictions .kanban-board header.kanban-done { background-color: #10b981; color: #fff; }
.kanban-board.disabled-board { opacity: 0.3; }
.drag_handler { margin-right: 8px; float: left; }
.drag_handler_grip {
float: left;
width: 16px;
height: 24px;
margin-right: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: grab;
opacity: .3;
transition: opacity .15s;
}
.drag_handler_grip:hover { opacity: .7; }
.drag_handler_grip_icon {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--p-text-muted-color);
box-shadow: 0 -6px 0 var(--p-text-muted-color), 0 6px 0 var(--p-text-muted-color);
}
</style>
<div class="card">
<h5>Touch Drag</h5>
<p>Set <code>touchDraggable</code> to enable touch drag-and-drop for mobile devices.</p>
<h:form id="formTouchDrag">
<p:messages id="messages" showSummary="true" showDetail="true"/>
<pe:kanban id="kanban"
widgetVar="kanbanTouchWidget"
value="#{kanbanController.columns}"
draggable="true"
touchDraggable="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>Board Dragging</h5>
<p>Reorder entire boards (columns) by dragging them. Use <code>dragBoards</code> to enable or disable it and the <i>dragBoard</i> / <i>dragendBoard</i> events to react.</p>
<h:form id="formBoardDrag">
<p:messages id="messages" showSummary="true" showDetail="true"/>
<div style="margin-bottom:8px">
<p:outputLabel for="dragBoards" value="Board Dragging" styleClass="font-bold" style="margin-right:8px" />
<p:selectOneMenu id="dragBoards" value="#{kanbanBoardDragController.dragBoards}" style="width:120px">
<f:selectItem itemLabel="Enabled" itemValue="true" />
<f:selectItem itemLabel="Disabled" itemValue="false" />
<p:ajax update="kanban" process="@this" />
</p:selectOneMenu>
</div>
<pe:kanban id="kanban"
widgetVar="kanbanBoardDragWidget"
value="#{kanbanBoardDragController.columns}"
draggable="true"
dragBoards="#{kanbanBoardDragController.dragBoards}"
style="height:450px">
<p:ajax event="drop" listener="#{kanbanBoardDragController.onDrop}" update="messages" />
<p:ajax event="dragBoard" listener="#{kanbanBoardDragController.onDragBoard}" update="messages" />
<p:ajax event="dragendBoard" listener="#{kanbanBoardDragController.onDragendBoard}" update="messages" />
</pe:kanban>
</h:form>
</div>
<div class="card">
<h5>Drag Handles</h5>
<p>With <code>dragHandle</code> only the handle icon of an item is draggable, which prevents accidental drags on cards with interactive content. The handle style is set via the <code>extender</code>.</p>
<h:form id="formDragHandle">
<p:messages id="messages" showSummary="true" showDetail="true"/>
<div style="margin-bottom:8px">
<p:outputLabel for="dragHandle" value="Drag Handle" styleClass="font-bold" style="margin-right:8px" />
<p:selectOneMenu id="dragHandle" value="#{kanbanDragHandleController.dragHandle}" style="width:120px">
<f:selectItem itemLabel="Enabled" itemValue="true" />
<f:selectItem itemLabel="Disabled" itemValue="false" />
<p:ajax update="kanban" process="@this" />
</p:selectOneMenu>
<p:outputLabel for="handleStyle" value="Handle Style" styleClass="font-bold" style="margin:0 8px 0 16px" />
<p:selectOneMenu id="handleStyle" value="#{kanbanDragHandleController.handleStyle}" style="width:140px">
<f:selectItem itemLabel="Default" itemValue="default" />
<f:selectItem itemLabel="Grip" itemValue="grip" />
<p:ajax update="kanban" process="@this" />
</p:selectOneMenu>
</div>
<pe:kanban id="kanban"
widgetVar="kanbanDragHandleWidget"
value="#{kanbanDragHandleController.columns}"
draggable="true"
dragHandle="#{kanbanDragHandleController.dragHandle}"
extender="#{kanbanDragHandleController.extender}"
style="height:400px">
<p:ajax event="drop" listener="#{kanbanDragHandleController.onDrop}" update="messages" />
</pe:kanban>
</h:form>
<h:outputScript>
function kanbanDragHandleDefault() {}
function kanbanDragHandleGrip() {
this.cfg.itemHandleOptions = {
customCssHandler: 'drag_handler_grip',
customCssIconHandler: 'drag_handler_grip_icon'
};
}
</h:outputScript>
</div>
<div class="card">
<h5>Drop Restrictions</h5>
<p>Use the <code>dragTo</code> property of a column to list the columns items may be dropped into. Items follow a strict workflow (Backlog, In Progress, Review, Done); restricted columns are dimmed while dragging and disallowed drops are cancelled.</p>
<h:form id="formDropRestrictions">
<p:messages id="messages" showSummary="true" showDetail="true"/>
<pe:kanban id="kanban"
widgetVar="kanbanDropRestrictionsWidget"
value="#{kanbanDropRestrictionsController.columns}"
draggable="true"
style="height:450px">
<p:ajax event="drop" listener="#{kanbanDropRestrictionsController.onDrop}" update="messages" />
</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;
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.KanbanBoardDragEvent;
import org.primefaces.extensions.event.KanbanDragEvent;
import org.primefaces.extensions.model.kanban.KanbanColumn;
import org.primefaces.extensions.model.kanban.KanbanItem;
/**
* KanbanBoardDragController
*
* @since 16.0.0
*/
@Named
@ViewScoped
public class KanbanBoardDragController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<KanbanColumn> columns;
private boolean dragBoards = true;
private List<String> boardOrder;
@PostConstruct
public void init() {
columns = new ArrayList<>();
KanbanColumn discoveryColumn = new KanbanColumn("discovery", "Discovery");
discoveryColumn.setCssClass("kanban-discovery");
discoveryColumn.addItem(new KanbanItem("item1", "User research",
"Conduct user interviews and surveys"));
discoveryColumn.addItem(new KanbanItem("item2", "Market analysis",
"Analyze competitors and market trends"));
KanbanColumn designColumn = new KanbanColumn("design", "Design");
designColumn.setCssClass("kanban-design");
designColumn.addItem(new KanbanItem("item3", "Wireframes",
"Create low-fidelity wireframes"));
designColumn.addItem(new KanbanItem("item4", "Prototype",
"Build interactive prototype"));
KanbanColumn developmentColumn = new KanbanColumn("development", "Development");
developmentColumn.setCssClass("kanban-development");
developmentColumn.addItem(new KanbanItem("item5", "Frontend",
"Implement UI components"));
developmentColumn.addItem(new KanbanItem("item6", "Backend",
"Build REST API endpoints"));
KanbanColumn testingColumn = new KanbanColumn("testing", "Testing");
testingColumn.setCssClass("kanban-testing");
testingColumn.addItem(new KanbanItem("item7", "Unit tests",
"Write unit tests for all modules"));
testingColumn.addItem(new KanbanItem("item8", "E2E tests",
"Run end-to-end test suite"));
columns.add(discoveryColumn);
columns.add(designColumn);
columns.add(developmentColumn);
columns.add(testingColumn);
boardOrder = new ArrayList<>();
for (KanbanColumn col : columns) {
boardOrder.add(col.getId());
}
}
public void onDragBoard(KanbanBoardDragEvent event) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Board Drag Started",
"Board \"" + event.getBoardId() + "\" is being dragged");
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void onDragendBoard(KanbanBoardDragEvent event) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Board Drag Ended",
"Board \"" + event.getBoardId() + "\" moved to position " + event.getNewPosition());
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void onDrop(KanbanDragEvent event) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Moved",
"Item \"" + event.getItemId() + "\" moved from " + event.getSourceColumnId()
+ " to " + event.getTargetColumnId());
FacesContext.getCurrentInstance().addMessage(null, message);
}
public List<KanbanColumn> getColumns() {
return columns;
}
public void setColumns(List<KanbanColumn> columns) {
this.columns = columns;
}
public List<String> getBoardOrder() {
return boardOrder;
}
public void setBoardOrder(List<String> boardOrder) {
this.boardOrder = boardOrder;
}
public boolean isDragBoards() {
return dragBoards;
}
public void setDragBoards(boolean dragBoards) {
this.dragBoards = dragBoards;
}
}
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.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.event.KanbanDragEvent;
import org.primefaces.extensions.model.kanban.KanbanColumn;
import org.primefaces.extensions.model.kanban.KanbanItem;
@Named
@ViewScoped
public class KanbanDragHandleController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<KanbanColumn> columns;
private boolean dragHandle = true;
private String handleStyle = "default";
@PostConstruct
public void init() {
columns = new ArrayList<>();
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"));
KanbanColumn inProgressColumn = new KanbanColumn("inprogress", "In Progress");
inProgressColumn.setCssClass("kanban-inprogress");
inProgressColumn.addItem(new KanbanItem("item3", "Design component",
"Design the Kanban board component"));
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 List<KanbanColumn> getColumns() {
return columns;
}
public boolean isDragHandle() {
return dragHandle;
}
public void setDragHandle(boolean dragHandle) {
this.dragHandle = dragHandle;
}
public String getHandleStyle() {
return handleStyle;
}
public void setHandleStyle(String handleStyle) {
this.handleStyle = handleStyle;
}
public String getExtender() {
if (!dragHandle) {
return null;
}
if ("grip".equals(handleStyle)) {
return "kanbanDragHandleGrip";
}
return "kanbanDragHandleDefault";
}
}
package org.primefaces.extensions.showcase.controller;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
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.KanbanDragEvent;
import org.primefaces.extensions.model.kanban.KanbanColumn;
import org.primefaces.extensions.model.kanban.KanbanItem;
/**
* @since 16.0.0
*/
@Named
@ViewScoped
public class KanbanDropRestrictionsController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<KanbanColumn> columns;
@PostConstruct
public void init() {
columns = new ArrayList<>();
KanbanColumn backlogColumn = new KanbanColumn("backlog", "Backlog");
backlogColumn.setCssClass("kanban-backlog");
backlogColumn.setDragTo(Arrays.asList("inprogress"));
backlogColumn.addItem(new KanbanItem("item1", "Research requirements",
"Gather and analyze project requirements"));
backlogColumn.addItem(new KanbanItem("item2", "Plan sprint",
"Define sprint goals and tasks"));
KanbanColumn inProgressColumn = new KanbanColumn("inprogress", "In Progress");
inProgressColumn.setCssClass("kanban-inprogress");
inProgressColumn.setDragTo(Arrays.asList("review"));
inProgressColumn.addItem(new KanbanItem("item3", "Implement feature X",
"Core implementation of feature X"));
inProgressColumn.addItem(new KanbanItem("item4", "Write unit tests",
"Add unit tests for new modules"));
KanbanColumn reviewColumn = new KanbanColumn("review", "Review");
reviewColumn.setCssClass("kanban-review");
reviewColumn.setDragTo(Arrays.asList("inprogress", "done"));
reviewColumn.addItem(new KanbanItem("item5", "Code review PR #42",
"Review pull request for feature X"));
reviewColumn.addItem(new KanbanItem("item6", "QA verification",
"Verify bug fixes in staging"));
KanbanColumn doneColumn = new KanbanColumn("done", "Done");
doneColumn.setCssClass("kanban-done");
doneColumn.setDragTo(Arrays.asList("review"));
doneColumn.addItem(new KanbanItem("item7", "Deploy v1.0",
"Production deployment completed"));
doneColumn.addItem(new KanbanItem("item8", "Documentation",
"API documentation updated"));
columns.add(backlogColumn);
columns.add(inProgressColumn);
columns.add(reviewColumn);
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());
FacesContext.getCurrentInstance().addMessage(null, message);
}
public List<KanbanColumn> getColumns() {
return columns;
}
public void setColumns(List<KanbanColumn> columns) {
this.columns = columns;
}
}