Skip to main content
Back to examples
Popular features/Task label position
Loading live demo…from dhtmlxcode.com
Popular features

Task label position

Move task labels above, below, or inside the bar and choose what each label shows, such as the owner, risk, or title.

gantt.plugins({ tooltip: true });

		gantt.config.date_format = "%Y-%m-%d";
		gantt.config.auto_types = true;
		gantt.config.open_tree_initially = true;
		gantt.config.grid_resize = true;
		gantt.config.row_height = 64;
		gantt.config.bar_height = 26;
		gantt.config.grid_width = 360;
		gantt.config.scale_height = 52;
		gantt.config.min_column_width = 34;
		gantt.config.start_date = new Date(2026, 6, 1);
		gantt.config.end_date = new Date(2026, 7, 7);
		gantt.config.scales = [
			{ unit: "month", step: 1, format: "%F %Y" },
			{ unit: "day", step: 1, format: "%j" }
		];

		let fields = {
			duration: "Duration",
			text: "Task name",
			owner: "Owner",
			start: "Start date",
			progress: "Progress",
			risk: "Risk"
		};

		let slots = [
			{ id: "before", label: "Before" },
			{ id: "inside", label: "Inside" },
			{ id: "after", label: "After" },
			{ id: "top", label: "Top" },
			{ id: "bottom", label: "Bottom" }
		];

		let labelState = {
			before: { enabled: true, field: "duration" },
			inside: { enabled: true, field: "text" },
			after: { enabled: true, field: "owner" },
			top: { enabled: false, field: "start" },
			bottom: { enabled: false, field: "progress" }
		};

		let dateToStr = gantt.date.date_to_str("%M %d");

		/* Store owner options in a named Gantt collection. The same collection is
		   used by labels, grid templates, tooltips and the built-in lightbox select. */
		let ownerOptions = gantt.serverList("owners", [
			{ key: "", label: "Unassigned", color: "#94a3b8" },
			{ key: "ana", label: "Ana Kim", color: "#2563eb" },
			{ key: "li", label: "Li Wei", color: "#0891b2" },
			{ key: "mia", label: "Mia Cruz", color: "#16a34a" },
			{ key: "omar", label: "Omar Diallo", color: "#e11d48" }
		]);

		gantt.locale.labels.section_owner = "Owner";
		gantt.config.lightbox.sections = [
			{ name: "description", height: 70, map_to: "text", type: "textarea", focus: true },
			{ name: "owner", height: 30, map_to: "owner", type: "select", options: ownerOptions },
			{ name: "time", height: 72, map_to: "auto", type: "duration" }
		];
		gantt.config.lightbox.milestone_sections = [
			{ name: "description", height: 70, map_to: "text", type: "textarea", focus: true },
			{ name: "owner", height: 30, map_to: "owner", type: "select", options: ownerOptions },
			{ name: "time", height: 72, map_to: "auto", type: "duration", single_date: true }
		];

		let demoData = {
			tasks: [
				{ id: 1, text: "Product launch", type: "project", open: true },
				{ id: 11, text: "Positioning brief", parent: 1, start_date: "2026-07-06", duration: 4, progress: .9, owner: "ana", stage: "content", risk: "clear" },
				{ id: 12, text: "Pricing page copy", parent: 1, start_date: "2026-07-09", duration: 5, progress: .55, owner: "li", stage: "content", risk: "review" },
				{ id: 13, text: "Checkout prototype", parent: 1, start_date: "2026-07-13", duration: 6, progress: .4, owner: "mia", stage: "design" },
				{ id: 14, text: "Billing integration", parent: 1, start_date: "2026-07-18", duration: 5, progress: .2, owner: "omar", stage: "build", risk: "blocked" },
				{ id: 15, text: "QA sign-off", parent: 1, start_date: "2026-07-24", duration: 3, progress: 0, owner: "mia", stage: "review", risk: "late" },
				{ id: 16, text: "Launch review", parent: 1, type: "milestone", start_date: "2026-07-28", owner: "ana", stage: "review" },
				{ id: 2, text: "Marketing rollout", type: "project", open: true },
				{ id: 21, text: "Campaign brief", parent: 2, start_date: "2026-07-08", duration: 4, progress: .85, owner: "li", stage: "content", risk: "clear" },
				{ id: 22, text: "Landing page design", parent: 2, start_date: "2026-07-13", duration: 5, progress: .5, owner: "mia", stage: "design", risk: "review" },
				{ id: 23, text: "Email sequence build", parent: 2, start_date: "2026-07-18", duration: 4, progress: .3, owner: "ana", stage: "build" },
				{ id: 24, text: "Social media assets", parent: 2, start_date: "2026-07-20", duration: 6, progress: .25, owner: "li", stage: "build", risk: "late" },
				{ id: 25, text: "Press outreach", parent: 2, start_date: "2026-07-27", duration: 4, progress: .1, owner: "omar", stage: "review", risk: "blocked" },
				{ id: 26, text: "Campaign go-live", parent: 2, type: "milestone", start_date: "2026-08-03", owner: "li", stage: "review" }
			],
			links: [
				{ id: 1, source: 11, target: 12, type: "0" },
				{ id: 2, source: 12, target: 13, type: "0" },
				{ id: 3, source: 13, target: 14, type: "0" },
				{ id: 4, source: 14, target: 15, type: "0" },
				{ id: 5, source: 15, target: 16, type: "0" },
				{ id: 6, source: 21, target: 22, type: "0" },
				{ id: 7, source: 22, target: 23, type: "0" },
				{ id: 8, source: 23, target: 24, type: "0" },
				{ id: 9, source: 24, target: 25, type: "0" },
				{ id: 10, source: 25, target: 26, type: "0" }
			]
		};

		function esc(value) {
			return String(value || "").replace(/[&<>"']/g, function (char) {
				return {
					"&": "&amp;",
					"<": "&lt;",
					">": "&gt;",
					'"': "&quot;",
					"'": "&#39;"
				}[char];
			});
		}

		function owner(task) {
			for (let i = 0; i < ownerOptions.length; i++) {
				if (String(ownerOptions[i].key) === String(task.owner || "")) {
					return ownerOptions[i];
				}
			}
			return null;
		}

		function ownerLabel(task) {
			let item = owner(task);
			if (!item) {
				return "";
			}
			return "<span class='label-owner'><span class='label-avatar' style='background:" + item.color + "'>" +
				DHX.ui.initials(item.label) + "</span>" + esc(item.label) + "</span>";
		}

		function riskInfo(task) {
			let map = {
				clear: { text: "On track", css: "clear" },
				review: { text: "! Review", css: "review" },
				blocked: { text: "! Blocked", css: "blocked" },
				late: { text: "! Late", css: "late" }
			};
			return map[task.risk] || map.clear;
		}

		function riskLabel(task) {
			let risk = riskInfo(task);
			return "<span class='label-risk label-risk--" + risk.css + "'>" + esc(risk.text) + "</span>";
		}

		function fieldText(task, field) {
			if (field === "duration") {
				return task.type === gantt.config.types.milestone ? "Milestone" : task.duration + "d";
			}
			if (field === "text") {
				return task.text;
			}
			if (field === "owner") {
				let item = owner(task);
				return item ? item.label : "";
			}
			if (field === "start") {
				return dateToStr(task.start_date);
			}
			if (field === "progress") {
				return Math.round((task.progress || 0) * 100) + "%";
			}
			if (field === "risk") {
				return riskInfo(task).text;
			}
			return "";
		}

		function labelHtml(task, field, slot) {
			if (gantt.isSummaryTask(task)) {
				return "";
			}
			if (field === "owner" && slot !== "inside") {
				return ownerLabel(task);
			}
			if (field === "risk") {
				return riskLabel(task);
			}
			if (slot === "inside") {
				return "<span class='label-title'><span class='label-title__text'>" + esc(fieldText(task, field)) + "</span></span>";
			}
			return "<span class='label-pill'>" + esc(fieldText(task, field)) + "</span>";
		}

		function renderControls() {
			let html = slots.map(function (slot) {
				let state = labelState[slot.id];
				let menu = Object.keys(fields).map(function (field) {
					let checked = state.field === field ? " checked" : "";
					return "<div class='dhx_menu__item dhx_menu__item--check" + checked + "' data-field='" + field + "' onclick=\"setSlotField('" + slot.id + "', '" + field + "')\">" + fields[field] + "</div>";
				}).join("");

				return "<div class='label-control' data-slot='" + slot.id + "'>" +
					"<button class='label-toggle" + (state.enabled ? " active" : "") + "' onclick=\"toggleSlot('" + slot.id + "')\">" +
						"<span class='label-toggle__box'></span>" + slot.label +
					"</button>" +
					"<div class='dhx_dd' id='dd_" + slot.id + "'>" +
						"<button class='dhx_btn label-field' onclick=\"DHX.dd.toggle('dd_" + slot.id + "')\">" +
							"<span id='" + slot.id + "_field_label'>" + fields[state.field] + "</span>" +
							"<span class='dhx_caret'><svg class='dhx_ic dhx_ic--sm' viewBox='0 0 24 24'><polyline points='6 9 12 15 18 9'/></svg></span>" +
						"</button>" +
						"<div class='dhx_menu'>" + menu + "</div>" +
					"</div>" +
				"</div>";
			}).join("");
			document.getElementById("label_controls").innerHTML = html;
		}

		function syncControls() {
			slots.forEach(function (slot) {
				let state = labelState[slot.id];
				let wrap = document.querySelector(".label-control[data-slot='" + slot.id + "']");
				if (!wrap) {
					return;
				}
				wrap.querySelector(".label-toggle").classList.toggle("active", state.enabled);
				DHX.ui.setText(slot.id + "_field_label", fields[state.field]);
				wrap.querySelectorAll(".dhx_menu__item").forEach(function (item) {
					item.classList.toggle("checked", item.getAttribute("data-field") === state.field);
				});
			});
		}

		function applyLabelTemplates() {
			gantt.templates.leftside_text = function (start, end, task) {
				return labelState.before.enabled ? labelHtml(task, labelState.before.field, "before") : "";
			};
			gantt.templates.task_text = function (start, end, task) {
				if (!labelState.inside.enabled || gantt.isSummaryTask(task)) {
					return esc(task.text);
				}
				return labelHtml(task, labelState.inside.field, "inside");
			};
			gantt.templates.rightside_text = function (start, end, task) {
				return labelState.after.enabled ? labelHtml(task, labelState.after.field, "after") : "";
			};
		}

		function refreshLabels() {
			syncControls();
			applyLabelTemplates();
			gantt.render();
		}

		function toggleSlot(slotId) {
			labelState[slotId].enabled = !labelState[slotId].enabled;
			refreshLabels();
		}

		function setSlotField(slotId, field) {
			labelState[slotId].field = field;
			DHX.dd.closeAll();
			refreshLabels();
		}

		gantt.templates.task_class = function (start, end, task) {
			if (gantt.isSummaryTask(task)) {
				return "";
			}
			return "label-bar-" + (task.stage || "build");
		};

		gantt.templates.tooltip_text = function (start, end, task) {
			let item = owner(task);
			return "<b>" + esc(task.text) + "</b><br>" +
				(item ? "Owner: " + esc(item.label) + "<br>" : "") +
				(task.type === gantt.config.types.milestone ? "Milestone" : dateToStr(start) + " - " + dateToStr(end));
		};

		gantt.config.columns = [
			{ name: "text", label: "Task", tree: true, width: 210, resize: true },
			{ name: "owner", label: "Owner", width: 120, resize: true, template: function (task) {
				let item = owner(task);
				if (!item) {
					return "";
				}
				return "<span class='owner-cell'><span class='label-dot' style='background:" + item.color + "'></span>" + esc(item.label) + "</span>";
			}},
			{ name: "add", width: 40, resize: true }
		];

		gantt.addTaskLayer(function (task) {
			if (gantt.isSummaryTask(task) || (!labelState.top.enabled && !labelState.bottom.enabled)) {
				return null;
			}

			let pos = gantt.getTaskPosition(task, task.start_date, task.end_date);
			let wrapper = document.createElement("div");
			let center = pos.left + pos.width / 2;
			let rowHeight = pos.rowHeight || gantt.config.row_height;
			let labelHeight = 18;
			let labelGap = 2;

			if (labelState.top.enabled) {
				let top = document.createElement("div");
				top.className = "layer-label layer-label--top";
				top.textContent = fieldText(task, labelState.top.field);
				top.style.left = center + "px";
				top.style.top = (pos.top + labelGap) + "px";
				wrapper.appendChild(top);
			}

			if (labelState.bottom.enabled) {
				let bottom = document.createElement("div");
				bottom.className = "layer-label layer-label--bottom";
				bottom.textContent = fieldText(task, labelState.bottom.field);
				bottom.style.left = center + "px";
				bottom.style.top = (pos.top + rowHeight - labelHeight - labelGap) + "px";
				wrapper.appendChild(bottom);
			}

			return wrapper;
		});

		renderControls();
		applyLabelTemplates();
		DHX.dd.initClickOutside();
		gantt.init("gantt_here");
		gantt.parse(demoData);
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Task label position controls - dhtmlxGantt</title>
	<script src="../../codebase/dhtmlxgantt.js?v=10.0.0"></script>
	<link rel="stylesheet" href="../../codebase/dhtmlxgantt.css?v=10.0.0">
	<link rel="stylesheet" href="../common/demo-controls/dhx_controls.css">
	<script src="../common/demo-controls/dhx_controls.js"></script>
	<style>
		html, body {
			height: 100%;
			padding: 0;
			margin: 0;
			overflow: hidden;
		}

		body {
			display: flex;
			flex-direction: column;
			font-family: var(--dhx-font);
		}

		#gantt_here {
			width: 100%;
			flex: 1 1 auto;
			min-height: 0;
		}

		.label-toolbar {
			height: auto;
			min-height: 60px;
			padding-top: 10px;
			padding-bottom: 10px;
			white-space: normal;
		}

		.label-controls {
			display: flex;
			align-items: center;
			gap: 8px;
			flex-wrap: wrap;
			justify-content: center;
			min-width: 0;
		}

		.label-control {
			display: inline-flex;
			align-items: center;
			gap: 6px;
			padding: 3px;
			border: 1px solid var(--dhx-line);
			border-radius: 8px;
			background: var(--dhx-surface);
			white-space: nowrap;
		}

		.label-toggle {
			display: inline-flex;
			align-items: center;
			gap: 6px;
			height: 28px;
			padding: 0 7px;
			border: 0;
			border-radius: 6px;
			background: transparent;
			color: var(--dhx-ink-2);
			font: 600 12px var(--dhx-font);
			cursor: pointer;
		}

		.label-toggle:hover {
			background: var(--dhx-surface-2);
			color: var(--dhx-ink);
		}

		.label-toggle__box {
			display: inline-flex;
			align-items: center;
			justify-content: center;
			width: 16px;
			height: 16px;
			border: 1px solid #b8c2cc;
			border-radius: 4px;
			color: #fff;
			background: var(--dhx-surface);
			box-sizing: border-box;
			font-size: 12px;
			line-height: 1;
		}

		.label-toggle.active .label-toggle__box {
			background: var(--dhx-accent);
			border-color: var(--dhx-accent);
		}

		:root[data-gantt-theme="dark"] .label-toggle__box { border-color: #3a424e; }

		.label-toggle.active .label-toggle__box::before {
			content: "\2713";
			font-weight: 700;
		}

		.label-field {
			height: 28px;
			min-width: 112px;
			padding: 0 8px;
			justify-content: space-between;
			background: #f8fafc;
			border-color: var(--dhx-line);
			font-size: 12px;
		}

		.label-control .dhx_menu {
			top: 34px;
			min-width: 150px;
		}

		:root[data-gantt-theme="dark"] .label-field   { background: #1f242c; color: var(--dhx-ink); }
		:root[data-gantt-theme="dark"] .label-pill    { background: #2a313b; border-color: #39424f; color: #c3ccd8; }

		.label-pill,
		.label-owner,
		.label-avatar,
		.label-title {
			display: inline-flex;
			align-items: center;
			white-space: nowrap;
			line-height: 1;
		}

		.label-pill {
			height: 24px;
			padding: 0 8px;
			border-radius: 12px;
			color: #334155;
			background: #f1f5f9;
			border: 1px solid #dbe3ec;
			font-size: 12px;
			font-weight: 600;
		}

		.label-owner {
			gap: 6px;
			color: #334155;
			font-size: 12px;
			font-weight: 600;
		}

		.label-avatar {
			justify-content: center;
			width: 24px;
			height: 24px;
			border-radius: 50%;
			color: #fff;
			font-size: 10px;
			font-weight: 700;
			box-shadow: 0 0 0 2px #fff;
		}

		.label-dot {
			width: 9px;
			height: 9px;
			border-radius: 50%;
			flex: none;
		}

		.label-risk {
			display: inline-flex;
			align-items: center;
			gap: 5px;
			height: 24px;
			padding: 0 8px;
			border-radius: 12px;
			font-size: 12px;
			font-weight: 600;
			line-height: 1;
			white-space: nowrap;
		}

		.label-risk--review {
			color: #9f1239;
			background: #fff1f2;
			border: 1px solid #fecdd3;
		}

		.label-risk--blocked {
			color: var(--dhx-danger-ink);
			background: var(--dhx-danger-bg);
			border: 1px solid var(--dhx-danger-line);
		}

		.label-risk--late {
			color: var(--dhx-warn-ink);
			background: var(--dhx-warn-bg);
			border: 1px solid var(--dhx-warn-line);
		}

		.label-risk--clear {
			color: var(--dhx-ok-ink);
			background: var(--dhx-ok-bg);
			border: 1px solid var(--dhx-ok-line);
		}

		:root[data-gantt-theme="dark"] .label-risk--review  { color: #ffa2b3; background: #3a2028; border-color: #5c2a38; }

		.label-title {
			gap: 6px;
			height: 100%;
			min-width: 0;
			padding: 0 8px;
			font-size: 12px;
			font-weight: 600;
		}

		.label-title__text {
			min-width: 0;
			overflow: hidden;
			text-overflow: ellipsis;
			white-space: nowrap;
		}

		.layer-label {
			position: absolute;
			box-sizing: border-box;
			height: 18px;
			max-width: 180px;
			padding: 0 7px;
			border-radius: 8px;
			color: #475569;
			background: rgba(248, 250, 252, .96);
			border: 1px solid #dbe3ec;
			font-size: 11px;
			font-weight: 600;
			line-height: 16px;
			white-space: nowrap;
			overflow: hidden;
			text-overflow: ellipsis;
			pointer-events: none;
			transform: translateX(-50%);
			z-index: 2;
		}

		.layer-label--top {
			color: #0f766e;
			background: #f0fdfa;
			border-color: #99f6e4;
		}

		.layer-label--bottom {
			color: #6d28d9;
			background: #f5f3ff;
			border-color: #ddd6fe;
		}

		:root[data-gantt-theme="dark"] .layer-label--top    { color: #5eead4; background: #12332f; border-color: #1d4d47; }
		:root[data-gantt-theme="dark"] .layer-label--bottom { color: #c4b5fd; background: #251f3d; border-color: #3b3163; }

		.owner-cell {
			display: inline-flex;
			align-items: center;
			gap: 8px;
		}

		.owner-cell .label-dot {
			width: 10px;
			height: 10px;
		}

		.gantt_task_line.label-bar-design {
			background: #2563eb;
			border-color: #1d4ed8;
		}

		.gantt_task_line.label-bar-content {
			background: #0891b2;
			border-color: #0e7490;
		}

		.gantt_task_line.label-bar-build {
			background: #16a34a;
			border-color: #15803d;
		}

		.gantt_task_line.label-bar-review {
			background: #e11d48;
			border-color: #be123c;
		}

		.gantt_side_content {
			display: flex;
			align-items: center;
			line-height: 1;
			overflow: visible;
		}

		.gantt_side_content.gantt_left {
			padding-right: 12px;
		}

		.gantt_side_content.gantt_right {
			padding-left: 12px;
		}

		.gantt_side_content.gantt_link_crossing {
			margin-top: 0;
		}
	</style>
</head>
<body>
	<div class="dhx_header dhx_header--centered label-toolbar">
		<div class="label-controls" id="label_controls"></div>
	</div>

	<div id="gantt_here"></div>

	
</body>
</html>