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

Task styling

Compute each bar's color, progress color, and text color straight from data fields, with a manual override still available per task.

gantt.plugins({ tooltip: true });

gantt.config.date_format   = "%Y-%m-%d %H:%i";
gantt.config.row_height    = 38;
gantt.config.bar_height    = 26;
gantt.config.scale_height  = 54;
gantt.config.work_time     = true;
gantt.config.min_column_width = 46;

gantt.config.scales = [
	{ unit: "month", step: 1, format: "%F %Y" },
	{ unit: "week",  step: 1, format: "Week %W" }
];

/* Weekend shading. */
gantt.templates.timeline_cell_class = function (task, date) {
	return gantt.isWorkTime({ date, task }) ? "" : "week_end";
};

/* ---------------------------------------------------------------------------
 *  PALETTE: one accent per phase, plus matching darker progress shade.
 * ------------------------------------------------------------------------ */
let PHASES = {
	discovery:   { label: "Discovery",   bar: "#7c3aed", progress: "#5b21b6" },
	design:      { label: "Design",      bar: "#db2777", progress: "#9d174d" },
	development: { label: "Development", bar: "#2563eb", progress: "#1d4ed8" },
	qa:          { label: "QA & Testing", bar: "#d97706", progress: "#b45309" },
	launch:      { label: "Launch",      bar: "#059669", progress: "#047857" }
};

let PRIORITY = {
	high:   { label: "High",   bar: "#e5484d", progress: "#b42318", dot: "dhx_dot--high" },
	medium: { label: "Medium", bar: "#f5a623", progress: "#c77700", dot: "dhx_dot--medium" },
	low:    { label: "Low",    bar: "#3aa757", progress: "#26803f", dot: "dhx_dot--low" }
};

let STATUS = {
	done:    { label: "Done",        bar: "#16a34a", progress: "#15803d" },
	active:  { label: "In progress", bar: "#2563eb", progress: "#1d4ed8" },
	planned: { label: "Not started", bar: "#94a3b8", progress: "#64748b" }
};

function statusOf(task) {
	if (task.progress >= 1)  return "done";
	if (task.progress > 0)   return "active";
	return "planned";
}

/* ---------------------------------------------------------------------------
 *  DATA: "Mobile App Launch", one colour family per phase.
 *  Each task already carries its style fields (phase / priority / color).
 * ------------------------------------------------------------------------ */
let data = {
	tasks: [
		{ id: 1, text: "Mobile App Launch", type: "project", open: true, progress: 0.46 },

		{ id: 10, text: "Discovery", type: "project", parent: 1, open: true, phase: "discovery" },
		{ id: 11, text: "Market research",       start_date: "2026-09-01", duration: 5,  parent: 10, phase: "discovery", priority: "medium", progress: 1 },
		{ id: 12, text: "User interviews",        start_date: "2026-09-08", duration: 4,  parent: 10, phase: "discovery", priority: "high",   progress: 1 },
		{ id: 13, text: "Requirements approved",  start_date: "2026-09-12", type: "milestone", parent: 10, phase: "discovery" },

		{ id: 20, text: "Design", type: "project", parent: 1, open: true, phase: "design" },
		{ id: 21, text: "Wireframes",             start_date: "2026-09-15", duration: 5,  parent: 20, phase: "design", priority: "high",   progress: 1 },
		{ id: 22, text: "Visual design",          start_date: "2026-09-22", duration: 8,  parent: 20, phase: "design", priority: "high",   progress: 0.8 },
		{ id: 23, text: "Design system",          start_date: "2026-09-24", duration: 6,  parent: 20, phase: "design", priority: "medium", progress: 0.6 },

		{ id: 30, text: "Development", type: "project", parent: 1, open: true, phase: "development" },
		{ id: 31, text: "Project setup",          start_date: "2026-10-06", duration: 3,  parent: 30, phase: "development", priority: "low",    progress: 1 },
		{ id: 32, text: "Auth & onboarding",      start_date: "2026-10-09", duration: 7,  parent: 30, phase: "development", priority: "high",   progress: 0.5 },
		{ id: 33, text: "Core features",          start_date: "2026-10-20", duration: 12, parent: 30, phase: "development", priority: "high",   progress: 0.25 },
		{ id: 34, text: "Push notifications",     start_date: "2026-10-27", duration: 6,  parent: 30, phase: "development", priority: "medium", progress: 0 },
		{ id: 35, text: "Offline sync",           start_date: "2026-11-03", duration: 5,  parent: 30, phase: "development", priority: "medium", progress: 0 },
		{ id: 36, text: "Analytics integration",  start_date: "2026-11-06", duration: 4,  parent: 30, phase: "development", priority: "low",    progress: 0 },

		{ id: 40, text: "QA & Testing", type: "project", parent: 1, open: true, phase: "qa" },
		{ id: 41, text: "Test plan",              start_date: "2026-11-10", duration: 3,  parent: 40, phase: "qa", priority: "medium", progress: 0 },
		{ id: 42, text: "Regression testing",     start_date: "2026-11-13", duration: 6,  parent: 40, phase: "qa", priority: "high",   progress: 0 },
		{ id: 44, text: "Performance testing",    start_date: "2026-11-17", duration: 4,  parent: 40, phase: "qa", priority: "medium", progress: 0 },
		{ id: 45, text: "Accessibility audit",    start_date: "2026-11-19", duration: 3,  parent: 40, phase: "qa", priority: "low",    progress: 0 },
		{ id: 43, text: "Beta feedback",          start_date: "2026-11-21", duration: 5,  parent: 40, phase: "qa", priority: "low",    progress: 0 },

		{ id: 50, text: "Launch", type: "project", parent: 1, open: true, phase: "launch" },
		{ id: 51, text: "Store submission",       start_date: "2026-11-27", duration: 4,  parent: 50, phase: "launch", priority: "high",   progress: 0 },
		{ id: 52, text: "Marketing campaign",     start_date: "2026-12-01", duration: 5,  parent: 50, phase: "launch", priority: "medium", progress: 0 },
		{ id: 53, text: "Go live",                start_date: "2026-12-08", type: "milestone", parent: 50, phase: "launch" }
	],
	links: [
		{ id: 1, source: 11, target: 12, type: "0" },
		{ id: 2, source: 12, target: 13, type: "0" },
		{ id: 3, source: 13, target: 21, type: "0" },
		{ id: 4, source: 21, target: 22, type: "0" },
		{ id: 5, source: 22, target: 31, type: "0" },
		{ id: 6, source: 31, target: 32, type: "0" },
		{ id: 7, source: 32, target: 33, type: "0" },
		{ id: 8, source: 33, target: 41, type: "0" },
		{ id: 9, source: 42, target: 51, type: "0" },
		{ id: 10, source: 51, target: 53, type: "0" }
	]
};

/* ---------------------------------------------------------------------------
 *  COLOR ENGINE: derive each task's colors from its data fields.
 *  A manual override (set via the palette) wins over the active mode.
 * ------------------------------------------------------------------------ */
function colorsForTask(task, mode) {
	if (mode === "priority" && task.priority) return PRIORITY[task.priority];
	if (mode === "status")                    return STATUS[statusOf(task)];
	return PHASES[task.phase] || PHASES.development;     // default: phase
}

function applyMode(mode) {
	gantt.eachTask(function (task) {
		if (task.type === gantt.config.types.project) return;   // keep summaries neutral
		if (task.$manualColor) return;                          // respect overrides
		let color = colorsForTask(task, mode);
		task.color         = color.bar;
		task.progressColor = color.progress;
		task.textColor     = "#ffffff";
	});
	gantt.render();
	renderLegend(mode);
}

function setMode(mode) {
	DHX.ui.setActiveByAttribute("#color_seg .dhx_btn", "data-mode", mode, "active");
	applyMode(mode);
}

/* ---------------------------------------------------------------------------
 *  LEGEND: reflects whichever mode is active.
 * ------------------------------------------------------------------------ */
function renderLegend(mode) {
	let map = mode === "priority" ? PRIORITY : mode === "status" ? STATUS : PHASES;
	let html = Object.keys(map).map(function (key) {
		return '<span class="legend__item">' +
			'<span class="legend__swatch" style="background:' + map[key].bar + '"></span>' +
			map[key].label + '</span>';
	}).join("");
	document.getElementById("legend").innerHTML = html;
}

/* ---------------------------------------------------------------------------
 *  GRID: task name, priority dot, duration.
 * ------------------------------------------------------------------------ */
function priorityCell(task) {
	if (!task.priority || task.type === gantt.config.types.project) return "";
	let priority = PRIORITY[task.priority];
	return '<span class="prio_cell"><span class="dhx_dot ' + priority.dot + '"></span>' + priority.label + '</span>';
}

gantt.config.columns = [
	{ name: "text",     label: "Task name", tree: true, width: 230, resize: true },
	{ name: "priority", label: "Priority",  align: "left", width: 110, resize: true, template: priorityCell },
	{ name: "duration", label: "Days",      align: "center", width: 60, resize: true },
	{ name: "add",      width: 40, resize: true }
];

/* ---------------------------------------------------------------------------
 *  TOOLTIP
 * ------------------------------------------------------------------------ */
gantt.templates.tooltip_text = function (start, end, task) {
	if (task.type === gantt.config.types.milestone) {
		return "<b>" + task.text + "</b><br>" + gantt.templates.tooltip_date_format(start);
	}
	return "<b>" + task.text + "</b><br>" +
		gantt.templates.tooltip_date_format(start) + " &ndash; " + gantt.templates.tooltip_date_format(end) +
		"<br>Progress: " + Math.round((task.progress || 0) * 100) + "%";
};

/* ---------------------------------------------------------------------------
 *  LIGHTBOX: keep manual color / text-color pickers available.
 * ------------------------------------------------------------------------ */
let lightboxColors = [
	{ key: "",        label: "Auto (by data)" },
	{ key: "#7c3aed", label: "Violet" },
	{ key: "#db2777", label: "Pink" },
	{ key: "#2563eb", label: "Blue" },
	{ key: "#059669", label: "Green" },
	{ key: "#d97706", label: "Amber" },
	{ key: "#e5484d", label: "Red" },
	{ key: "#0f172a", label: "Charcoal" }
];

gantt.locale.labels.section_color     = "Bar color";
gantt.locale.labels.section_textColor = "Text color";
gantt.config.lightbox.sections = [
	{ name: "description", height: 38, map_to: "text", type: "textarea", focus: true },
	{ name: "color",       height: 22, map_to: "color",     type: "select", options: lightboxColors },
	{ name: "textColor",   height: 22, map_to: "textColor", type: "select", options: lightboxColors },
	{ name: "time",        type: "duration", map_to: "auto" }
];

/* When a task is edited to a manual color in the lightbox, mark it as overridden. */
gantt.attachEvent("onLightboxSave", function (id, task) {
	task.$manualColor = !!task.color;
	return true;
});

/* ---------------------------------------------------------------------------
 *  RECOLOR PALETTE: override the selected task's color.
 * ------------------------------------------------------------------------ */
let SWATCHES = ["#7c3aed", "#db2777", "#2563eb", "#059669", "#d97706",
	"#e5484d", "#0ea5e9", "#14b8a6", "#f59e0b", "#0f172a"];
let selectedId = null;

function buildSwatches() {
	document.getElementById("swatches").innerHTML = SWATCHES.map(function (c) {
		return '<div class="swatch" style="background:' + c + '" onclick="recolorSelected(\'' + c + '\')"></div>';
	}).join("");
}

function recolorSelected(color) {
	if (selectedId == null) return;
	let task = gantt.getTask(selectedId);
	task.color = color;
	task.progressColor = color;
	task.textColor = "#ffffff";
	task.$manualColor = true;          // survives mode switches
	gantt.updateTask(selectedId);
	DHX.dd.close("dd_recolor");
}

gantt.attachEvent("onTaskSelected", function (id) {
	let task = gantt.getTask(id);
	if (task.type === gantt.config.types.project) return;
	selectedId = id;
	document.getElementById("btn_recolor").disabled = false;
});

gantt.attachEvent("onTaskUnselected", function () {
	selectedId = null;
	document.getElementById("btn_recolor").disabled = true;
	DHX.dd.close("dd_recolor");
});

DHX.dd.initClickOutside();

/* ---------------------------------------------------------------------------
 *  INIT
 * ------------------------------------------------------------------------ */
function sizeGantt() {
	document.getElementById("gantt_here").style.height = (window.innerHeight - 60) + "px";
}
window.addEventListener("resize", sizeGantt);
sizeGantt();

buildSwatches();
gantt.init("gantt_here");
gantt.parse(data);
applyMode("phase");          // paint bars from data on first load
gantt.selectTask(12);        // preselect a task so "Recolor task" starts active
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Task styling: 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>
		#gantt_here { 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_task_cell.week_end { background-color: rgba(15,23,42,.035); }

		.prio_cell { display: inline-flex; align-items: center; gap: 7px; }
		.prio_cell .dhx_dot { width: 9px; height: 9px; }

		.ts_header { position: relative; }
		.ts_header .dhx_header__center { position: absolute; left: 50%; transform: translateX(-50%); margin: 0; z-index: 5; }

		.legend { display: inline-flex; align-items: center; gap: 14px; }
		.legend__item {
			display: inline-flex;
			align-items: center;
			gap: 6px;
			font: 500 12px var(--dhx-font);
			color: var(--dhx-ink-2);
			white-space: nowrap;
		}
		.legend__swatch {
			width: 12px;
			height: 12px;
			border-radius: 4px;
			flex: none;
			box-shadow: inset 0 0 0 1px rgba(16,24,40,.12);
		}

		#menu_recolor { min-width: 0; }
		.swatches {
			display: grid;
			grid-template-columns: repeat(5, 26px);
			gap: 7px;
			padding: 4px;
		}
		.swatch {
			box-sizing: border-box;
			width: 26px;
			height: 26px;
			border-radius: 7px;
			cursor: pointer;
			border: 2px solid #fff;
			box-shadow: 0 0 0 1px rgba(16,24,40,.12);
			transition: transform .1s;
		}
		.swatch:hover { transform: scale(1.12); }
	</style>
</head>
<body>

<div class="dhx_header ts_header">
	<!-- Legend (updates with the active mode) -->
	<div class="legend" id="legend"></div>

	<div class="dhx_header__center">
		<!-- Coloring strategy -->
		<span style="font:600 13px var(--dhx-font);color:var(--dhx-ink)">Color by</span>
		<div class="dhx_seg" id="color_seg">
			<button class="dhx_btn active" data-mode="phase"    onclick="setMode('phase')">Phase</button>
			<button class="dhx_btn"        data-mode="priority" onclick="setMode('priority')">Priority</button>
			<button class="dhx_btn"        data-mode="status"   onclick="setMode('status')">Status</button>
		</div>

		<div class="dhx_sep"></div>

		<!-- Recolor selected task -->
		<div class="dhx_dd" id="dd_recolor">
			<button class="dhx_btn dhx_btn--surface" id="btn_recolor" onclick="DHX.dd.toggle('dd_recolor')" disabled>
				<svg class="dhx_ic" viewBox="0 0 16 16"><path d="M2 11.5 10 3.5l2.5 2.5-8 8H2z"/><path d="M9 4.5 11.5 7"/></svg>
				Recolor task
				<span class="dhx_caret"><svg class="dhx_ic dhx_ic--sm" viewBox="0 0 12 12"><polyline points="2 4 6 8 10 4"/></svg></span>
			</button>
			<div class="dhx_menu" id="menu_recolor">
				<div class="swatches" id="swatches"></div>
			</div>
		</div>
	</div>
</div>

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


</body>
</html>