• Settings
    • Locale
    • Change the locale of the datepicker, schedule and client side validation messages.

      Language
      English
      English
      English
      French
      German
      German
      German
      Italian
      Korean
      Spanish
      Catalan
      Dutch
      Portuguese
      Portuguese
      Arabic
      Arabic
      Bulgarian
      Bangla
      Bosnian
      Czech
      Greek
      Estonian
      Persian
      Finnish
      Danish
      Hindi
      Indonesian
      Icelandic
      Croatian
      Japanese
      Hungarian
      Hebrew
      Georgian
      Central Kurdish
      Khmer
      Kyrgyz
      Kazakh
      Lithuanian
      Latvian
      Malay
      Norwegian
      Polish
      Romanian
      Russian
      Slovak
      Slovenian
      Serbian
      Serbian
      Swedish
      Thai
      Turkish
      Ukrainian
      Uzbek
      Vietnamese
      Chinese
      Chinese
  • Resources

Kanban Interaction

Kanban items react to clicks, right-clicks and the client-side widget API. 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.

Kanban Tag Documentation

Kanban board component for visualizing workflow and managing tasks with drag-and-drop functionality.

NameTypeRequiredDefaultDescription
id String false generated Unique identifier of the component in a namingContainer.
rendered boolean false true Boolean value to specify the rendering of the component, when set to false component will not be rendered.
binding UIComponent false generated An EL expression referring to a server side UIComponent instance in a backing bean.
widgetVar String false generated ('widget_' + componentClientId) Name of the client side widget.
addItemButton boolean false false Enable add item button on each board.
bindContextMenu String false Client-side id of a PrimeFaces contextMenu component to bind to right-click events.
dragBoards boolean false true Enable board (column) dragging and reordering.
dragHandle boolean false false Enable drag handle on items. When enabled, only the handle area is draggable, preventing accidental drags on the card body.
draggable boolean false true Enable drag-and-drop functionality.
extender String false JavaScript function to extend the widget configuration.
gutter String false 15px Gutter (spacing) between boards in CSS units, e.g. "15px".
responsivePercentage boolean false false If true, boards use percentage-based widths instead of fixed pixel widths.
style String false Inline CSS style of the component.
styleClass String false Style class of the component.
touchDraggable boolean false false Enable touch drag-and-drop on mobile devices.
value List true List of KanbanColumn objects representing the kanban board columns and items.
widthBoard String false 250px Default width of each board in CSS units, e.g. "250px".
Tag Documentation Widget Source Basic Interaction Dragging
Item Interaction

Click an item on the board to view and edit its details in a dialog (the itemClick event).

Right-Click Context Menu

The bindContextMenu attribute and the itemRightClick event open a PrimeFaces context menu on a task with options to edit, view history or delete. The addItemButton of each column adds new tasks.

Delete Task
Are you sure you want to delete this task?
Client API

The widget exposes a client-side API via PF(widgetVar).methodName(): addElement, findElement, findBoard, getParentBoardID, getBoardElements, replaceElement and removeBoard. Try them with the toolbar; click a task to fill the item id.

Board
Item
Selected: click a task

Usage


<style>
    .kanban-item-title {
        font-weight: 600;
    }

    .kanban-item-description {
        font-size: 0.8rem;
        opacity: 0.7;
        margin-top: 2px;
    }

    #formItemInteraction .kanban-board header.kanban-todo,
    #formRightClick .kanban-board header.kanban-todo { background-color: #4f46e5; color: #fff; }
    #formItemInteraction .kanban-board header.kanban-inprogress,
    #formRightClick .kanban-board header.kanban-inprogress { background-color: #ea580c; color: #fff; }
    #formItemInteraction .kanban-board header.kanban-done,
    #formRightClick .kanban-board header.kanban-done { background-color: #16a34a; color: #fff; }
    #formRightClick .kanban-board header { font-size: 1rem; }

    #formClientApi .kanban-board header.kanban-backlog { background-color: #6366f1; color: #fff; }
    #formClientApi .kanban-board header.kanban-inprogress { background-color: #f59e0b; color: #fff; }
    #formClientApi .kanban-board header.kanban-done { background-color: #10b981; color: #fff; }
    .api-toolbar { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 12px; margin-bottom: 12px; }
    .api-toolbar .row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
    .api-toolbar .row:last-child { margin-bottom: 0; }
    .api-toolbar .sep { border-left: 1px solid #d1d5db; height: 24px; margin: 0 4px; }
    .board-highlight { outline: 3px solid #6366f1; outline-offset: 2px; border-radius: 4px; }
    .item-highlight { outline: 3px solid #f59e0b; outline-offset: 2px; border-radius: 4px; }
    .selected-badge { background: #eef2ff; color: #4f46e5; padding: 2px 10px; border-radius: 4px; font-size: .8rem; font-weight: 600; }
    .result-pill { background: #ecfdf5; color: #059669; padding: 2px 10px; border-radius: 4px; font-size: .8rem; font-weight: 600; display: none; }
    .result-pill.visible { display: inline-block; }
</style>

<div class="card">
    <h5>Item Interaction</h5>
    <p>Click an item on the board to view and edit its details in a dialog (the <i>itemClick</i> event).</p>
    <h:form id="formItemInteraction">
        <p:messages id="messages" showSummary="true" showDetail="true"/>

        <pe:kanban id="kanban"
                    widgetVar="kanbanItemWidget"
                    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" />
            <p:ajax event="itemClick" listener="#{kanbanController.onItemClick}" update="dialogContent" oncomplete="PF('itemDialog').show()" />
        </pe:kanban>

        <p:dialog id="dialog" header="Task Details" widgetVar="itemDialog" modal="true" responsive="true" width="400">
            <h:panelGroup id="dialogContent" layout="block" styleClass="p-fluid">
                <h:panelGroup rendered="#{not empty kanbanController.selectedItem}">
                    <div class="field mb-3">
                        <p:outputLabel for="title" value="Title" styleClass="font-bold block mb-1" />
                        <p:inputText id="title" value="#{kanbanController.selectedItem.title}" required="true" />
                    </div>
                    <div class="field mb-3">
                        <p:outputLabel for="desc" value="Description" styleClass="font-bold block mb-1" />
                        <p:inputTextarea id="desc" value="#{kanbanController.selectedItem.description}" rows="3" />
                    </div>
                    <div class="flex justify-content-end gap-2 mt-4">
                        <p:commandButton value="Save" action="#{kanbanController.saveItem}" update="kanban messages" oncomplete="PF('itemDialog').hide()" styleClass="p-button-primary mr-2" />
                        <p:commandButton value="Cancel" onclick="PF('itemDialog').hide()" type="button" styleClass="p-button-secondary" />
                    </div>
                </h:panelGroup>
            </h:panelGroup>
        </p:dialog>
    </h:form>
</div>

<div class="card">
    <h5>Right-Click Context Menu</h5>
    <p>The <code>bindContextMenu</code> attribute and the <i>itemRightClick</i> event open a PrimeFaces context menu on a task with options to edit, view history or delete. The <code>addItemButton</code> of each column adds new tasks.</p>
    <h:form id="formRightClick">
        <p:messages id="messages" showSummary="true" showDetail="true"/>

        <pe:kanban id="kanban"
                    widgetVar="kanbanRightClickWidget"
                    value="#{kanbanRightClickController.columns}"
                    draggable="true"
                    addItemButton="true"
                    style="height:420px"
                    bindContextMenu="kanbanContextMenu">
            <p:ajax event="itemRightClick" listener="#{kanbanRightClickController.onItemRightClick}" />
            <p:ajax event="itemAdd" listener="#{kanbanRightClickController.onItemAdd}" update="kanban messages" />
        </pe:kanban>

        <p:contextMenu id="contextMenu" for="kanban" widgetVar="kanbanContextMenu" targetFilter=".kanban-item">
            <p:menuitem value="Edit Task"
                        icon="pi pi-pencil"
                        update="dialogContent"
                        oncomplete="PF('taskDialog').show()" />
            <p:divider />
            <p:menuitem value="View History"
                        icon="pi pi-clock"
                        update="historyContent"
                        oncomplete="PF('historyDialog').show()" />
            <p:menuitem value="Delete Task"
                        icon="pi pi-trash"
                        styleClass="text-red-600"
                        onclick="PF('confirmDelete').show()" />
        </p:contextMenu>

        <p:confirmDialog id="confirmDeleteDialog" widgetVar="confirmDelete"
                         showEffect="fade" hideEffect="fade"
                         message="Are you sure you want to delete this task?"
                         header="Delete Task" icon="pi pi-exclamation-triangle"
                         closeOnEscape="true" width="400">
            <p:commandButton value="Delete"
                             styleClass="p-button-danger mr-2"
                             actionListener="#{kanbanRightClickController.deleteSelectedItem}"
                             process="@this"
                             update="kanban messages"
                             oncomplete="PF('confirmDelete').hide()"
                             icon="pi pi-check" />
            <p:commandButton value="Cancel"
                             onclick="PF('confirmDelete').hide()"
                             type="button"
                             styleClass="p-button-secondary"
                             icon="pi pi-times" />
        </p:confirmDialog>

        <p:dialog header="Task Details" widgetVar="taskDialog"
                  modal="true" responsive="true" width="400"
                  showEffect="fade" hideEffect="fade"
                  closeOnEscape="true">
            <h:panelGroup id="dialogContent" layout="block" styleClass="p-fluid">
                <h:panelGroup rendered="#{not empty kanbanRightClickController.selectedItem}">
                    <div class="field mb-3">
                        <p:outputLabel for="title" value="Title" styleClass="font-bold block mb-1" />
                        <p:inputText id="title" value="#{kanbanRightClickController.selectedItem.title}" readonly="true" />
                    </div>
                    <div class="field mb-3">
                        <p:outputLabel for="desc" value="Description" styleClass="font-bold block mb-1" />
                        <p:inputTextarea id="desc" value="#{kanbanRightClickController.selectedItem.description}" rows="3" readonly="true" />
                    </div>
                </h:panelGroup>
                <h:panelGroup rendered="#{empty kanbanRightClickController.selectedItem}">
                    <p>Right-click on a task and select "Edit Task" to see its details here.</p>
                </h:panelGroup>
            </h:panelGroup>
        </p:dialog>

        <p:dialog header="Task History" widgetVar="historyDialog"
                  modal="true" responsive="true" width="400"
                  showEffect="fade" hideEffect="fade"
                  closeOnEscape="true">
            <h:panelGroup id="historyContent" layout="block" styleClass="p-fluid">
                <h:panelGroup rendered="#{not empty kanbanRightClickController.selectedItem}">
                    <p>Activity history for <strong>#{kanbanRightClickController.selectedItem.title}</strong>:</p>
                    <ul>
                        <li>Created - Jan 15, 2026</li>
                        <li>Moved to #{kanbanRightClickController.selectedColumnId} - Jan 20, 2026</li>
                        <li>Milestone updated - Feb 01, 2026</li>
                    </ul>
                </h:panelGroup>
                <h:panelGroup rendered="#{empty kanbanRightClickController.selectedItem}">
                    <p>Right-click on a task and select "View History" to see its activity timeline.</p>
                </h:panelGroup>
            </h:panelGroup>
        </p:dialog>
    </h:form>
</div>

<div class="card">
    <h5>Client API</h5>
    <p>The widget exposes a client-side API via <code>PF(widgetVar).methodName()</code>: <code>addElement</code>, <code>findElement</code>, <code>findBoard</code>, <code>getParentBoardID</code>, <code>getBoardElements</code>, <code>replaceElement</code> and <code>removeBoard</code>. Try them with the toolbar; click a task to fill the item id.</p>
    <h:form id="formClientApi">
        <p:growl id="growl" widgetVar="growlClientApi" showDetail="true" showSummary="true"/>


        <p:remoteCommand name="rcAddItem"       actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onAddItem()" />
        <p:remoteCommand name="rcRemoveBoard"   actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onRemoveBoard()" />
        <p:remoteCommand name="rcCountItems"    actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onCountItems()" />
        <p:remoteCommand name="rcFindItem"      actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onFindItem()" />
        <p:remoteCommand name="rcParentBoard"   actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onParentBoard()" />
        <p:remoteCommand name="rcReplaceItem"   actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onReplaceItem()" />
        <p:remoteCommand name="rcHighlightBoard" actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onHighlightBoard()" />

        <div class="api-toolbar">
            <div class="row">
                <span style="font-weight:bold;min-width:50px">Board</span>
                <p:selectOneMenu id="boardSelect" style="width:150px">
                    <f:selectItem itemLabel="Backlog" itemValue="backlog" />
                    <f:selectItem itemLabel="In Progress" itemValue="inprogress" />
                    <f:selectItem itemLabel="Done" itemValue="done" />
                </p:selectOneMenu>
                <p:commandButton value="Add Item" icon="pi pi-plus" type="button"
                                 styleClass="p-button-sm"
                                 onclick="rcAddItem()" />
                <p:commandButton value="Count Items" icon="pi pi-list" type="button"
                                 styleClass="p-button-sm"
                                 onclick="rcCountItems()" />
                <span class="result-pill" id="countResult"></span>
                <p:commandButton value="Remove Board" icon="pi pi-trash" type="button"
                                 styleClass="p-button-sm p-button-danger"
                                 onclick="rcRemoveBoard()" />
                <p:commandButton value="Highlight Board" icon="pi pi-eye" type="button"
                                 styleClass="p-button-sm"
                                 onclick="rcHighlightBoard()" />
            </div>
            <div class="row">
                <span style="font-weight:bold;min-width:50px">Item</span>
                <p:inputText id="itemIdInput" placeholder="e.g. t-1" style="width:130px" />
                <h:panelGroup id="selectedItemBadge" layout="block" style="display:inline">
                    <span class="selected-badge">
                        Selected: #{kanbanClientApiController.lastClickedItemId != null ? kanbanClientApiController.lastClickedItemId : 'click a task'}
                    </span>
                </h:panelGroup>
                <span class="sep"></span>
                <p:commandButton value="Find" icon="pi pi-search" type="button"
                                 styleClass="p-button-sm"
                                 onclick="rcFindItem()" />
                <p:commandButton value="Parent Board" icon="pi pi-arrow-right" type="button"
                                 styleClass="p-button-sm"
                                 onclick="rcParentBoard()" />
                <span class="result-pill" id="parentResult"></span>
                <p:commandButton value="Replace Title" icon="pi pi-pencil" type="button"
                                 styleClass="p-button-sm"
                                 onclick="rcReplaceItem()" />
            </div>
        </div>

        <pe:kanban id="kanban"
                    widgetVar="kanbanClientApiWidget"
                    value="#{kanbanClientApiController.columns}"
                    draggable="true"
                    addItemButton="true"
                    style="height:400px">
            <p:ajax event="itemClick" listener="#{kanbanClientApiController.onItemClick}" update="growl selectedItemBadge"
                    oncomplete="KanbanDemo.onItemClick(args)" />
            <p:ajax event="itemAdd" update="kanban growl" />
        </pe:kanban>
    </h:form>
    <h:outputScript>
        var KanbanDemo = KanbanDemo || {};

        KanbanDemo.widget = function() {
            return PF('kanbanClientApiWidget');
        };

        KanbanDemo.boardId = function() {
            return $(PrimeFaces.escapeClientId('formClientApi:boardSelect_input')).val();
        };

        KanbanDemo.itemId = function() {
            return $(PrimeFaces.escapeClientId('formClientApi:itemIdInput')).val();
        };

        KanbanDemo.notify = function(summary, detail, severity) {
            severity = severity || 'info';
            PF('growlClientApi').show([{
                summary: summary,
                detail: detail,
                severity: severity
            }]);
        };

        KanbanDemo.notifyError = function(summary, detail) {
            KanbanDemo.notify(summary, detail, 'error');
        };

        KanbanDemo.notifyWarn = function(summary, detail) {
            KanbanDemo.notify(summary, detail, 'warn');
        };

        KanbanDemo.flashBoard = function(boardEl) {
            boardEl.classList.add('board-highlight');
            setTimeout(function() {
                boardEl.classList.remove('board-highlight');
            }, 2000);
        };

        KanbanDemo.flashItem = function(itemEl) {
            itemEl.classList.add('item-highlight');
            setTimeout(function() {
                itemEl.classList.remove('item-highlight');
            }, 2000);
        };

        KanbanDemo.showPill = function(pillId, text) {
            var $pill = $('#' + pillId);
            $pill.text(text).addClass('visible');
            setTimeout(function() {
                $pill.removeClass('visible');
            }, 3000);
        };

        KanbanDemo.hidePill = function(pillId) {
            $('#' + pillId).removeClass('visible').text('');
        };

        KanbanDemo.assertItemId = function() {
            var id = KanbanDemo.itemId();
            if (!id) {
                KanbanDemo.notifyError('No ID', 'Click a task or type an ID');
                return null;
            }
            return id;
        };

        KanbanDemo.onAddItem = function() {
            var widget = KanbanDemo.widget();
            var board = KanbanDemo.boardId();

            if (!widget.findBoard(board)) {
                KanbanDemo.notifyError('Board not found', 'Board "' + board + '" no longer exists');
                return;
            }

            widget.addElement(board, {
                id: 't-' + Date.now(),
                title: 'New Task',
                description: 'Added via client API'
            });

            KanbanDemo.notify('Item Added', 'New task added to ' + board);
        };

        KanbanDemo.onRemoveBoard = function() {
            var widget = KanbanDemo.widget();
            var board = KanbanDemo.boardId();

            if (!widget.findBoard(board)) {
                KanbanDemo.notifyWarn('Already removed', 'Board "' + board + '" does not exist');
                return;
            }

            widget.removeBoard(board);
            KanbanDemo.notifyWarn('Board Removed', 'Board removed: ' + board);
        };

        KanbanDemo.onCountItems = function() {
            var widget = KanbanDemo.widget();
            var board = KanbanDemo.boardId();
            var boardEl = widget.findBoard(board);
            var pillId = 'countResult';

            KanbanDemo.hidePill(pillId);

            if (!boardEl) {
                KanbanDemo.notifyError('Board not found', 'Board "' + board + '" no longer exists');
                return;
            }

            var count = boardEl.querySelectorAll('.kanban-item').length;
            KanbanDemo.flashBoard(boardEl);
            KanbanDemo.showPill(pillId, count + ' items');
            KanbanDemo.notify(count + ' items', 'Board "' + board + '" has ' + count + ' items');
        };

        KanbanDemo.onFindItem = function() {
            var widget = KanbanDemo.widget();
            var id = KanbanDemo.assertItemId();
            if (!id) return;

            var itemEl = widget.findElement(id);
            if (itemEl) {
                KanbanDemo.flashItem(itemEl);
                KanbanDemo.notify('Found', 'Item "' + id + '" highlighted');
            } else {
                KanbanDemo.notifyError('Not found', 'No item with ID "' + id + '"');
            }
        };

        KanbanDemo.onParentBoard = function() {
            var widget = KanbanDemo.widget();
            var id = KanbanDemo.assertItemId();
            var pillId = 'parentResult';
            KanbanDemo.hidePill(pillId);
            if (!id) return;

            var board = widget.getParentBoardID(id);
            if (board) {
                var boardEl = widget.findBoard(board);
                if (boardEl) {
                    KanbanDemo.flashBoard(boardEl);
                    KanbanDemo.showPill(pillId, 'In: ' + board);
                }
            }

            if (board) {
                KanbanDemo.notify('Parent Board', 'Item "' + id + '" is in board "' + board + '"');
            } else {
                KanbanDemo.notifyError('Not found', 'Item "' + id + '" not found');
            }
        };

        KanbanDemo.onReplaceItem = function() {
            var widget = KanbanDemo.widget();
            var id = KanbanDemo.assertItemId();
            if (!id) return;

            var itemEl = widget.findElement(id);
            if (!itemEl) {
                KanbanDemo.notifyError('Not found', 'No item with ID "' + id + '"');
                return;
            }

            widget.replaceElement(id, {
                title: 'Updated at ' + new Date().toLocaleTimeString()
            });

            KanbanDemo.notify('Replaced', 'Content replaced for "' + id + '"');
        };

        KanbanDemo.onHighlightBoard = function() {
            var widget = KanbanDemo.widget();
            var board = KanbanDemo.boardId();
            var boardEl = widget.findBoard(board);

            if (!boardEl) {
                KanbanDemo.notifyError('Board not found', 'Board "' + board + '" no longer exists');
                return;
            }

            KanbanDemo.flashBoard(boardEl);
            KanbanDemo.notify('Board Found', 'Board "' + board + '" highlighted');
        };

        KanbanDemo.onItemClick = function(args) {
            if (args.itemId) {
                $(PrimeFaces.escapeClientId('formClientApi:itemIdInput')).val(args.itemId);
            }
        };
    </h:outputScript>
</div>