Skip to main content
Back to examples
Popular features/Custom tooltips
Loading live demo…from dhtmlxcode.com
Popular features

Custom tooltips

Render a designed tooltip card for task bars and add targeted tooltips for dependency links and date-scale cells.

gantt.plugins({ tooltip: true });

gantt.message({
	text: "Hover a <b>task</b>, a <b>dependency line</b>, or the <b>date scale</b> &middot; templates.tooltip_text renders the task card, ext.tooltips.tooltipFor attaches bespoke tooltips to links and scale cells",
	expire: -1
});

gantt.config.date_format  = "%Y-%m-%d";
gantt.config.row_height    = 54;
gantt.config.bar_height    = 38;
gantt.config.scale_height  = 56;
gantt.config.min_column_width = 140;

gantt.config.scales = [
	{ unit: "month", step: 1, format: "%F %Y" },
	{ unit: "week",  step: 1, format: function (d) { return "Wk " + gantt.date.date_to_str("%W")(d); } }
];

gantt.templates.tooltip_date_format = gantt.date.date_to_str("%M %j, %Y");

let OWNERS = {
	ava:  { name: "Ava Reed",   color: "#7c3aed" },
	leo:  { name: "Leo Marsh",  color: "#2563eb" },
	mia:  { name: "Mia Fontana",color: "#059669" }
};

function statusOf(task) {
	if (task.type === gantt.config.types.milestone) return { label: "Milestone", bg: "#eef1f4", fg: "#334155", color: "#0f172a" };
	if (task.progress >= 1) return { label: "Done",        bg: "#e7f6ee", fg: "#067a4a", color: "#16a34a" };
	if (task.progress > 0)  return { label: "In progress", bg: "#e0edff", fg: "#1d4ed8", color: "#2563eb" };
	return { label: "Not started", bg: "#eef1f4", fg: "#64748b", color: "#94a3b8" };
}

gantt.config.columns = [
	{ name: "text",     label: "Activity", tree: true, width: 230, resize: true },
	{ name: "start_date", label: "Start", align: "center", width: 100, resize: true },
	{ name: "duration", label: "Days", align: "center", width: 58, resize: true },
	{ name: "add",      width: 40, resize: true }
];

/* Colour bars by status so the tooltip matches the chart. */
gantt.attachEvent("onTaskLoading", function (task) {
	if (task.type !== gantt.config.types.project) task.color = statusOf(task).color;
	return true;
});

let STATUS_DARK = {
	"Milestone":   { bg: "#2a313b", fg: "#c1c8d6", color: "#8fa3bd" },
	"Done":        { bg: "#12331f", fg: "#5fd99a", color: "#22c55e" },
	"In progress": { bg: "#1a2c47", fg: "#8fbcf7", color: "#3b82f6" },
	"Not started": { bg: "#2a313b", fg: "#98a2b3", color: "#94a3b8" }
};

function themed(st) {
	if (document.documentElement.getAttribute("data-gantt-theme") !== "dark") return st;
	let dark = STATUS_DARK[st.label];
	return dark ? { label: st.label, bg: dark.bg, fg: dark.fg, color: dark.color } : st;
}

/* --- the task tooltip card ---------------------------------------------- */
gantt.templates.tooltip_text = function (start, end, task) {
	let st = themed(statusOf(task));
	let pct = Math.round((task.progress || 0) * 100);
	let owner = OWNERS[task.owner];
	let html = '<div class="tt">' +
		'<div class="tt__bar" style="background:' + st.color + '"></div>' +
		'<div class="tt__body">' +
			'<div class="tt__title">' + task.text + "</div>" +
			'<span class="tt__chip" style="background:' + st.bg + ';color:' + st.fg + '">' + st.label + "</span>";

	if (task.type !== gantt.config.types.milestone) {
		html += '<div class="tt__row"><span>Start</span><b>' + gantt.templates.tooltip_date_format(start) + "</b></div>" +
			'<div class="tt__row"><span>Finish</span><b>' + gantt.templates.tooltip_date_format(new Date(end.valueOf() - 1)) + "</b></div>" +
			'<div class="tt__row"><span>Progress</span><b>' + pct + '%</b></div>' +
			'<div class="tt__prog"><i style="width:' + pct + "%;background:" + st.color + '"></i></div>';
	} else {
		html += '<div class="tt__row"><span>Date</span><b>' + gantt.templates.tooltip_date_format(start) + "</b></div>";
	}

	if (owner) {
		html += '<div class="tt__owner"><span class="tt__av" style="background:' + owner.color + '">' +
			DHX.ui.initials(owner.name) + "</span>" + owner.name + "</div>";
	}
	return html + "</div>";
};

function linkTypeToString(type) {
	let L = gantt.config.links;
	if (type == L.start_to_start)   return "Start to start";
	if (type == L.start_to_finish)  return "Start to finish";
	if (type == L.finish_to_start)  return "Finish to start";
	if (type == L.finish_to_finish) return "Finish to finish";
	return "";
}

gantt.attachEvent("onGanttReady", function () {
	let tooltips = gantt.ext.tooltips;

	/* Dependency link tooltip. */
	tooltips.tooltipFor({
		selector: ".gantt_task_link",
		html: function (event, node) {
			let linkId = node.getAttribute(gantt.config.link_attribute);
			if (!linkId) return;
			let link = gantt.getLink(linkId);
			return '<div class="tt--link"><b>' + linkTypeToString(link.type) + "</b><br>" +
				gantt.getTask(link.source).text + " &rarr; " + gantt.getTask(link.target).text + "</div>";
		}
	});

	/* Date-scale tooltip. */
	tooltips.tooltipFor({
		selector: ".gantt_scale_cell",
		html: function (event) {
			let pos = gantt.utils.dom.getRelativeEventPosition(event, gantt.$task_scale);
			return '<div class="tt--date">' + gantt.templates.tooltip_date_format(gantt.dateFromPos(pos.x)) + "</div>";
		}
	});
});

/* --- data ---------------------------------------------------------------- */
/* Three workstreams run in parallel (rather than one long sequential chain),
 * so each task spans more days and its label has room to render in full. */
let data = {
	tasks: [
		{ id: 1, text: "Autumn Campaign",     type: "project", open: true, progress: 0.4 },
		{ id: 11, text: "Creative concepts",         parent: 1, start_date: "2026-09-01", duration: 12, progress: 1,   owner: "ava" },
		{ id: 12, text: "Landing page & email build", parent: 1, start_date: "2026-09-13", duration: 15, progress: 0.6, owner: "leo" },
		{ id: 13, text: "Paid & organic launch",      parent: 1, start_date: "2026-09-28", duration: 18, progress: 0.3, owner: "mia" },
		{ id: 14, text: "Campaign launch",           parent: 1, type: "milestone", start_date: "2026-10-16" },
		{ id: 15, text: "Post-launch analytics & reporting", parent: 1, start_date: "2026-10-16", duration: 14, progress: 0, owner: "mia" },
		{ id: 16, text: "Case study & wrap report",   parent: 1, start_date: "2026-10-30", duration: 10, progress: 0, owner: "leo" },

		{ id: 2, text: "Platform Migration",  type: "project", open: true, progress: 0.4 },
		{ id: 21, text: "Architecture & requirements", parent: 2, start_date: "2026-09-01", duration: 14, progress: 1,   owner: "leo" },
		{ id: 22, text: "Core services migration",     parent: 2, start_date: "2026-09-15", duration: 20, progress: 0.5, owner: "mia" },
		{ id: 23, text: "Data migration & QA",         parent: 2, start_date: "2026-10-05", duration: 16, progress: 0.2, owner: "ava" },
		{ id: 24, text: "Cutover",                    parent: 2, type: "milestone", start_date: "2026-10-21" },
		{ id: 25, text: "Post-migration monitoring",   parent: 2, start_date: "2026-10-21", duration: 12, progress: 0, owner: "leo" },

		{ id: 3, text: "Ops Readiness",       type: "project", open: true, progress: 0.4 },
		{ id: 31, text: "Vendor onboarding",           parent: 3, start_date: "2026-09-08", duration: 12, progress: 1,   owner: "ava" },
		{ id: 32, text: "Support runbook & tooling",   parent: 3, start_date: "2026-09-20", duration: 15, progress: 0.4, owner: "mia" },
		{ id: 33, text: "Team training sessions",      parent: 3, start_date: "2026-10-05", duration: 10, progress: 0.1, owner: "leo" },
		{ id: 34, text: "Go-live readiness review",    parent: 3, type: "milestone", start_date: "2026-10-15" },
		{ id: 35, text: "Post go-live support",         parent: 3, start_date: "2026-10-15", duration: 14, progress: 0, owner: "ava" },

		{ id: 4, text: "Customer Success",    type: "project", open: true, progress: 0.4 },
		{ id: 41, text: "Onboarding playbook design",  parent: 4, start_date: "2026-09-01", duration: 12, progress: 1,   owner: "ava" },
		{ id: 42, text: "CRM integration setup",       parent: 4, start_date: "2026-09-13", duration: 18, progress: 0.5, owner: "leo" },
		{ id: 43, text: "Customer training rollout",   parent: 4, start_date: "2026-10-01", duration: 15, progress: 0.2, owner: "mia" },
		{ id: 44, text: "Feedback loop live",          parent: 4, type: "milestone", start_date: "2026-10-16" },
		{ id: 45, text: "Success metrics dashboard",   parent: 4, start_date: "2026-10-16", duration: 14, progress: 0, owner: "ava" },
		{ id: 46, text: "Renewal playbook update",     parent: 4, start_date: "2026-10-30", duration: 10, progress: 0, owner: "leo" }
	],
	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: 31, target: 32, type: "0" },
		{ id: 11, source: 32, target: 33, type: "0" },
		{ id: 12, source: 33, target: 34, type: "0" },
		{ id: 13, source: 34, target: 35, type: "0" },

		{ id: 14, source: 41, target: 42, type: "0" },
		{ id: 15, source: 42, target: 43, type: "0" },
		{ id: 16, source: 43, target: 44, type: "0" },
		{ id: 17, source: 44, target: 45, type: "0" },
		{ id: 18, source: 45, target: 46, type: "0" }
	]
};

gantt.init("gantt_here");
gantt.parse(data);
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Custom tooltips: 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>
	<link rel="preconnect" href="https://fonts.googleapis.com">
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
	<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
	<style>
		body { display: flex; flex-direction: column; }
		#gantt_here { flex: 1 1 auto; min-height: 0; width: 100%; }

		.gantt_task_line { border: none; border-radius: 7px; box-shadow: 0 1px 2px rgba(16,24,40,.18); }
		.gantt_task_line .gantt_task_progress { border-radius: 7px 0 0 7px; }
		.gantt_task_line.gantt_project { border-radius: 5px; }

		.gantt_tooltip {
			border: none !important;
			border-radius: 12px !important;
			box-shadow: 0 8px 24px rgba(16,24,40,.16), 0 2px 6px rgba(16,24,40,.10) !important;
			padding: 0 !important;
			font-family: var(--dhx-font) !important;
			background: var(--dhx-panel) !important;
			overflow: hidden;
		}
		.tt { width: 244px; }
		.tt__bar { height: 4px; }
		.tt__body { padding: 12px 14px 14px; }
		.tt__title { font: 600 14px var(--dhx-font); color: var(--dhx-ink); margin-bottom: 8px; }
		.tt__row { display: flex; justify-content: space-between; gap: 12px; font: 500 12px var(--dhx-font); color: var(--dhx-muted); margin-top: 4px; }
		.tt__row b { color: var(--dhx-ink-2); font-weight: 600; }
		.tt__chip { display: inline-block; padding: 2px 8px; border-radius: 999px; font: 600 11px var(--dhx-font); }
		.tt__prog { height: 6px; border-radius: 999px; background: var(--dhx-neutral-bg); margin-top: 10px; overflow: hidden; }
		:root[data-gantt-theme="dark"] .gantt_tooltip {
			box-shadow: 0 8px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.4) !important;
		}
		.tt__prog > i { display: block; height: 100%; border-radius: 999px; }
		.tt__owner { display: inline-flex; align-items: center; gap: 6px; margin-top: 10px; font: 600 12px var(--dhx-font); color: var(--dhx-ink-2); }
		.tt__av { width: 20px; height: 20px; border-radius: 50%; color: #fff; font: 700 10px var(--dhx-font); display: inline-flex; align-items: center; justify-content: center; }
		.tt--link { padding: 10px 12px; font: 500 12px var(--dhx-font); color: var(--dhx-ink-2); }
		.tt--link b { color: var(--dhx-ink); }
		.tt--date { padding: 8px 12px; font: 600 12px var(--dhx-font); color: var(--dhx-ink); }
	</style>
</head>
<body>

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


</body>
</html>