Skip to main content

DHTMLX Kanban. Template for column headers example

The default column header shows a collapse icon, the label, and a menu icon, but many teams need additional information: card counts, limit indicators, or custom actions. Column header templates let you fully control the header HTML while still integrating with the Kanban's built-in interactions like collapse, rename, and context menu.

Live example

const { Kanban, template } = kanban;

// escape html characters in a string
function escapeHTML(str) {
    return str.replace(
        /[&<>'"]/g,
        tag =>
            ({
                "&": "&amp;",
                "<": "&lt;",
                ">": "&gt;",
                "'": "&#39;",
                '"': "&quot;",
            }[tag] || tag)
    );
}

function columnHeaderTemplate({
    readonly,
    isMenuVisible,
    column,
    columnState,
    renaming,
}) {
    return `<div class="wx-collapse-icon" data-action=${"collapse"}>
        <i class=${column.collapsed ? "wxi-angle-right" : "wxi-angle-left"}></i>
    </div>
    ${
        !renaming && !column.collapsed
            ? `<div class="wx-label" data-action="rename">
        ${escapeHTML(column.label)}
        (${columnState.cardsCount})
        ${column.limit ? `(${columnState.cardsCount}/${columnState.totalLimit})` : ""}
    </div>`
            : ""
    }

    ${
        isMenuVisible && !readonly && !renaming && !column.collapsed
            ? `<div class="wx-menu" data-menu-id=${column.id}>
            <i class="wxi-dots-v"></i>
        </div>`
            : ""
    }`;
}

function customCollapsedColumn({ column, columnState }) {
    return `<div class="wx-collapsed-label">
        <div class="wx-label-text">${escapeHTML(column.label)} (${
        columnState?.cardsCount
    })</div>
    </div>`;
}

new Kanban("#root", {
    columns,
    cards,
    columnShape: {
        headerTemplate: template(props =>
            columnHeaderTemplate(props)
        ),
        collapsedTemplate: template(props =>
            customCollapsedColumn(props)
        ),
    },
});
<!-- component container -->
<div style="height: 100%; width: 100%" id="root"></div>

<!-- styles for header template -->

<style>
.wx-header .wx-column {
		padding: 12px
	}
	.wx-menu,
	.wx-collapse-icon,
    .wxi-angle-left {
        display: flex;
		cursor: pointer;
		font-size: var(--wx-font-size-md);
		color: var(--wx-color-font-alt);
	}
	.wx-collapsed-label {
		writing-mode: tb-rl;
		font-weight: 500;
		font-size: var(--wx-font-size);
		margin: 12px 0;
		display: flex;
		align-items: center;
		width: 100%;
		z-index: 5;
	}
	.wx-label-text {
		white-space: nowrap;
		transform: rotate(180deg);
		z-index: 5;
		pointer-events: none;
	}
	.wx-label {
		display: flex;
		align-items: center;
		font-weight: var(--wx-font-weight-md);
		font-size: var(--wx-font-size);
		overflow: hidden;
		text-overflow: ellipsis;
		width: 100%;
		height: 100%;
		margin-left: 4px;
	}
</style>

<script>
const columns = [
		{
			label: "Backlog",
			id: "backlog",
		},
		{
			label: "In progress",
			id: "inprogress",
		},
		{
			label: "Testing",
			id: "testing",
		},
		{
			label: "Done",
			id: "done",
		},
	];
    
    const cards = [
		{
			label: "Integration with Angular/React",
			column: "backlog",
		},
		{
			label: "Archive the cards/boards ",
			column: "backlog",
		},
		{
			label: "Searching and filtering",
			column: "backlog",
		},
		{
			label: "Set the tasks priorities",
			column: "inprogress",
		},
		{
			label: "Custom icons",
			column: "inprogress",
		},
		{
			label: "Integration with Gantt",
			column: "inprogress",
		},
		{
			label: "Drag and drop",
			column: "testing",
		},
		{
			label: "Adding images",
			column: "testing",
		},
		{
			label: "Create cards and lists from the UI and from code",
			column: "done",
		},
		{
			label: "Draw swimlanes",
			column: "done",
		},
		{
			label: "Progress bar",
			column: "done",
		},
	];
</script>

The code defines two template functions: columnHeaderTemplate renders the expanded header with a collapse icon, label with card count and limit display, and a menu icon; customCollapsedColumn renders a vertical label for collapsed columns. Both are passed to columnShape.headerTemplate and columnShape.collapsedTemplate wrapped in the template() helper. The template receives column, columnState (with cardsCount and totalLimit), readonly, isMenuVisible, and renaming flags, plus uses data-action attributes to hook into built-in Kanban actions.

Solution overview

  1. Create a header template function that receives { column, columnState, readonly, isMenuVisible, renaming }
  2. Create a collapsed template function that receives { column, columnState }
  3. Pass them via columnShape: { headerTemplate: template(fn), collapsedTemplate: template(fn) }
  4. Use data-action="collapse" and data-action="rename" on elements to bind built-in Kanban behaviors
  5. Use data-menu-id={column.id} to enable the context menu

Key points

  • template() wrapper is required: Always wrap template functions with kanban.template() before passing them to columnShape
  • data-action attributes drive interaction: The Kanban listens for data-action="collapse", data-action="rename", and data-menu-id on template elements. Without these, built-in features won't trigger
  • columnState provides live data: cardsCount and totalLimit update automatically as cards move. No manual tracking needed
  • HTML escaping: Always escape user-provided content (like column.label) in templates to prevent XSS

API reference

  • columnShape: Column appearance configuration including headerTemplate and collapsedTemplate
  • template: Wraps a function for use as a reactive template

Additional resources