Mermaid Gantt Chart: Syntax, Examples and 26 Tested Traps

On this page
  1. Step 1: one task
  2. Step 2: title, dateFormat and sections
  3. Step 3: ids and after
  4. Step 4: done, active, crit
  5. Step 5: milestones
  6. Step 6: excludes weekends
  7. Step 7: vertical markers
  8. Step 8: the axis
  9. Step 9: the today marker
  10. Step 10: links on tasks
  11. Colours: the theme variables recipe
  12. 26 mistyped lines, and what Mermaid did
  13. When a timeline fits better
  14. Where it renders
  15. Gantt charts in Jira and Confluence
  16. Sources

A team lead pastes a release plan into a Markdown file. It renders, it looks right, and it goes into the sprint review. Two weeks later someone notices that “Docs” ends six days after the release it was supposed to finish before. Nothing was red. Mermaid drew exactly what the text said, and the text said something other than what the author meant.

This page builds one plan, the release of version 4.2 in January 2027, one keyword at a time. At every step it shows the dates Mermaid computed, not the dates we expected. We rendered every snippet in a headless browser with Mermaid 11.17.2, 12.1.0 and 12.3.0 (the latest, 3 November 2026) on 14 November 2026, read the computed start and end of every task, and tried 26 common mistakes to see which ones Mermaid reports.

Short answer: a Mermaid gantt chart is the word gantt and one line per task: Name :tags, id, start, length. Tags (done, active, crit, milestone, vert) come first, the start is a date or after <id>, the length is a duration such as 3d. This is the finished plan:

gantt
  title Release 4.2
  dateFormat YYYY-MM-DD
  axisFormat %d %b
  tickInterval 1week
  weekday monday
  excludes weekends
  todayMarker off

  section Build
  Spec          :done, spec, 2027-01-04, 3d
  API           :active, api, after spec, 5d
  UI            :ui, after spec, 6d
  section Test
  QA            :crit, qa, after api ui, 4d
  Code freeze   :vert, freeze, 2027-01-18, 0d
  section Ship
  Release       :milestone, rel, after qa, 0d
  Release notes :notes, after qa, 2d

The same dates in all three versions. 17 of our 26 mistyped lines drew a chart with no error at all, so check the dates, not only that it renders.

The rendered Release 4.2 gantt chart from Mermaid 12.3.0. Three sections, Build, Test and Ship, over 4 to 25 January 2027 with week ticks on Mondays. Spec is a grey done bar from 4 to 6 January. API is a light blue active bar and UI a blue bar, both starting 7 January. QA is a red critical bar from 15 to 20 January. A navy vertical line labelled Code freeze crosses the chart on 18 January. Release is a diamond milestone on 21 January, followed by a short Release notes bar. Grey bands mark the weekends.

These are the dates Mermaid computed for the plan, read from its own task list after parsing. They were the same in 11.17.2, 12.1.0 and 12.3.0:

TaskThe line saysMermaid computed (working days)
Specdone, spec, 2027-01-04, 3dMon 4 to Wed 6 Jan
APIafter spec, 5dThu 7 to Wed 13 Jan
UIafter spec, 6dThu 7 to Thu 14 Jan
QAafter api ui, 4dFri 15 to Wed 20 Jan, weekend skipped
Code freezevert, 2027-01-18, 0dA line on Mon 18 Jan
Releasemilestone, after qa, 0dA diamond on Thu 21 Jan
Release notesafter qa, 2dThu 21 to Fri 22 Jan

Internally a task ends at 00:00 of the next day: Spec “ends” Thu 7 Jan 00:00, which is why API starts on Thursday. Move Spec’s date and the whole plan moves with it.

Step 1: one task

gantt
  Spec :2027-01-04, 3d

That is a complete chart. Mermaid computed Mon 4 Jan, 00:00 to Thu 7 Jan, 00:00: three days, and the bar stops where Thursday begins. Without a dateFormat line Mermaid reads dates as YYYY-MM-DD.

The part before the colon is the name, the part after it is the data. One item is read as the end (a length or a date). A start is optional, because “a task start date defaults to the end date of the preceding task”, as the Mermaid docs put it.

Step 2: title, dateFormat and sections

gantt
  title Release 4.2
  dateFormat YYYY-MM-DD
  section Build
  Spec :spec, 2027-01-04, 3d
  section Ship
  Release notes :2d

section Build starts a band; every task below it belongs to it until the next section. A section needs a name: a bare section line drew an empty chart in our test, without an error. Release notes has no start, so it starts when Spec stops: Thu 7 Jan.

dateFormat only describes how you write dates. It does not change the axis (that is axisFormat, step 8). dateFormat DD.MM.YYYY with 04.01.2027 works, so does D MMM YYYY with 4 Jan 2027.

Step 3: ids and after

gantt
  dateFormat YYYY-MM-DD
  section Build
  Spec :spec, 2027-01-04, 3d
  API  :api, after spec, 5d
  UI   :ui, after spec, 6d
  section Test
  QA   :qa, after api ui, 4d

The id is the item right before the start. after spec starts API where Spec stops (Thu 7 Jan). after api ui waits for the later of the two: UI stops at Wed 13 Jan, API at Tue 12, so QA starts Wed 13 Jan.

Two rules that cost time when you break them:

  • Ids may contain letters, digits, _ and -. A dot ends the id: with :spec.1, … and after spec.1, Mermaid looked for spec and put the task on today’s date.
  • An id that does not exist does not stop the chart either. after a9 started the task on today’s date (14 November 2026 in our run). Since 12.1.0 the browser console logs Gantt: the "after" statement references unknown task id(s): a9. 11.17.2 says nothing.

Order of lines does not matter for after: a task may point at an id that is defined further down.

until: stop when another task starts

gantt
  dateFormat YYYY-MM-DD
  section Build
  Code freeze :milestone, freeze, 2027-01-15, 0d
  Fix bugs    :2027-01-06, until freeze

until (Mermaid 10.9.0+) ends Fix bugs where Code freeze starts: Wed 6 to Fri 15 Jan. Combine both ends with after spec, until freeze. Do not combine until with excludes (step 6): the weekend days are added on top. In a draft of our plan, Docs :after spec, until rel ended on Wed 27 Jan, six days after rel. The bug is open as mermaid #5867.

Step 4: done, active, crit

gantt
  dateFormat YYYY-MM-DD
  section Build
  Spec :done, spec, 2027-01-04, 3d
  API  :active, api, after spec, 5d
  QA   :crit, qa, after api, 4d
  Hotfix :crit, active, hot, 2027-01-08, 1d

The docs say it plainly: tags “are optional, but if used, they must be specified first”. We tested what happens when they are not:

You writeResult in 11.17.2, 12.1.0 and 12.3.0
:done, spec, 2027-01-04, 3dGrey done bar
:spec, done, 2027-01-04, 3dError Cannot read properties of undefined (reading 'type')
:DONE, spec, 2027-01-04, 3dThe same error: tags are lower case
:crit, done, … or :crit, active, …Both apply: a done or active bar with a red border

The default colours: done is light grey, active is light blue, crit is red, a plain task is blue.

Step 5: milestones

gantt
  dateFormat YYYY-MM-DD
  section Test
  QA      :qa, 2027-01-15, 4d
  section Ship
  Release :milestone, rel, after qa, 0d

A milestone is a diamond. It still needs a length: :milestone, rel, 2027-01-21 without one stopped with Invalid date:rel, because Mermaid reads the last item as the end and the id as the start. The docs place the diamond at “initial date + duration / 2”, so 0d puts it exactly on the day and 1d half a day later. crit, milestone draws a red diamond, which is the closest thing to a milestone colour.

Step 6: excludes weekends

gantt
  dateFormat YYYY-MM-DD
  axisFormat %a %d
  tickInterval 1day
  excludes weekends
  todayMarker off
  section Build
  Spec, 5d :spec, 2027-01-04, 5d
  Code, 3d :after spec, 3d
A Mermaid 12.3.0 gantt chart with excludes weekends and one tick per day from Mon 04 to Thu 14 January 2027. The bar Spec, 5d runs from Monday 4 to the end of Friday 8. Saturday 9 and Sunday 10 are a grey band with no bar. The bar Code, 3d starts on Monday 11 and ends at the end of Wednesday 13.

With excludes weekends, 5d means five working days. The bar stops at the end of Friday, the weekend stays empty and after spec starts on Monday. Without the line, Code would start on Saturday and stop at the end of Monday. What else we tested:

LineWhat Mermaid did
weekend friday (11.0.0+)Friday and Saturday off: Thu + 3d works Thu, Sun, Mon
weekend sundayParse error: only friday and saturday are accepted
excludes sunday, mondayWorks: days of the week by name
excludes weekends 2027-12-24 2027-12-31Holidays: a 5d task from Thu 23 Dec works 23, 27, 28, 29, 30 Dec
A second excludes lineAdded to the first (11.16.0+), handy with a %% comment per group
includes 2027-01-09That Saturday counts as a working day again
excludes weekdaysIgnored. Nothing excluded, no warning
All seven days excludedError Failed to find a valid date that was not excluded by excludes after 10,000 iterations.
A fixed end date, 2027-01-07, 2027-01-12Not extended: an end you write is kept
2027-01-08 14:00, 16hEnds Saturday 06:00: hours are not moved past a weekend (#7062)

The 10,000 limit came with 11.15.0, which fixed a freeze when every date was excluded (CVE-2026-41150). An excludes date in YYYY-MM-DD works even when your dateFormat is different.

Step 7: vertical markers

gantt
  dateFormat YYYY-MM-DD
  section Build
  Code        :code, 2027-01-04, 10d
  Code freeze :vert, freeze, 2027-01-11, 0d

vert (Mermaid 11.7.0+) draws a line across the whole chart with its name under the axis. “Unlike milestones, vertical markers don’t take up a row”, the docs say. Use it for a code freeze, a holiday or a deadline. crit, vert draws it red.

Step 8: the axis

gantt
  dateFormat YYYY-MM-DD
  axisFormat %d %b
  tickInterval 1week
  weekday monday
  section Build
  Spec :2027-01-04, 40d

axisFormat uses d3 time codes, not the dateFormat letters: %d %b printed “04 Jan”, %a %d “Mon 04”, W%W the week number (“W01”), %b %Y “Feb 2027”. tickInterval takes a number and millisecond, second, minute, hour, day, week or month.

LineWhat we saw
tickInterval 1weekTicks on Sundays (“Sun 10, Sun 17…”): the docs’ default
tickInterval 1week + weekday mondayTicks on Mondays
tickInterval 1decadeIgnored without a word, default ticks
tickInterval 1second over 20 daysIgnored, console warning “would generate 1728000 ticks”
topAxisError u.TopAxis is not a function in all three versions
Front matter config: gantt: topAxis: trueWorks: dates above and below

The topAxis keyword is in the grammar, but the grammar calls TopAxis() while the code exports enableTopAxis(). Use the config line until that is fixed.

Step 9: the today marker

Mermaid draws a red line on today’s date by default. In a plan that you paste into a document, that line moves every day. todayMarker off removes it; todayMarker stroke-width:4px,stroke:#DC2626,opacity:0.8 styles it.

gantt
  dateFormat YYYY-MM-DD
  section Build
  Spec :spec, 2027-01-04, 3d
  click spec href "https://example.com/spec"

The docs say clicks are “disabled when using securityLevel='strict'”. In our test that is only half true. A click … href link worked in strict mode: clicking the bar opened the URL. Mermaid only cleans the URL first: javascript:alert(1) became about:blank. click spec call myFunction() did nothing in strict mode and ran the function in loose mode. Whether a link works in your tool depends on the tool: many viewers show the SVG as a picture, and a picture has no links.

Colours: the theme variables recipe

Mermaid has no colour keyword for a task. You change the colours of each status through theme variables:

---
config:
  theme: base
  themeVariables:
    taskBkgColor: "#BFDBFE"
    taskBorderColor: "#2563EB"
    activeTaskBkgColor: "#FDE68A"
    activeTaskBorderColor: "#D97706"
    doneTaskBkgColor: "#BBF7D0"
    doneTaskBorderColor: "#16A34A"
    critBkgColor: "#FECACA"
    critBorderColor: "#DC2626"
---
gantt
  dateFormat YYYY-MM-DD
  axisFormat %d %b
  todayMarker off
  section Status
  Done     :done, d1, 2027-01-04, 2d
  Active   :active, a1, after d1, 3d
  Critical :crit, c1, after a1, 2d
  Plain    :after c1, 2d
A Mermaid 12.3.0 gantt chart with custom colours from theme variables. Done is a green bar with a dark green border, Active a yellow bar with an orange border, Critical a pink bar with a red border, and Plain a light blue bar with a blue border, one after the other from 4 to 13 January 2027.

Every variable took effect in all three versions. theme: dark and theme: forest change the whole palette; the older %%{init: {"theme": "forest"}}%% line still works too.

One task in its own colour

themeCSS: "#spec { … }" does not work: Mermaid prefixes every id with the diagram’s id, so the bar is <diagram id>-spec, and the page that renders the chart picks that prefix. Match the end of the id instead. This turned only the Spec bar purple, in all three versions:

---
config:
  themeCSS: '[id$="-spec"] { fill: #A78BFA; }'
---
gantt
  dateFormat YYYY-MM-DD
  section Build
  Spec :spec, 2027-01-04, 3d
  Code :code, after spec, 3d

Pick an id that no other task ends with (-spec would also match api-spec). Some tools drop themeCSS for safety, so check it where the chart will live.

Size is configuration as well. barHeight, barGap, fontSize, sectionFontSize and leftPadding go in the same gantt: block (with barHeight: 30 our one-task chart grew from 113 to 125 pixels), and useWidth: 600 made the chart 600 pixels wide instead of the width of its container. displayMode: compact puts tasks that do not overlap into one row.

26 mistyped lines, and what Mermaid did

A chart of 26 mistyped Mermaid gantt lines, each rendered in Mermaid 11.17.2, 12.1.0 and 12.3.0 with the same result in all three. A stacked bar shows 9 that show an error, 14 that draw a chart that is wrong, and 3 that are ignored: 17 of 26 give no error message. Errors: a tag after the id or in upper case (reading 'type'), a milestone without a duration (Invalid date), a start like next monday, the topAxis keyword (u.TopAxis is not a function), weekend sunday, all seven days excluded (10,000 iterations), and tasks named click tracking or gantt review (parse errors). Wrong charts: an unknown id after or until gives today's date, an id with a dot, 3dX or 3 d or 3D or a trailing comment give zero length, a colon cuts the name, #42; turns into a star, tasks named title page or section review become the title or a section, 04.01.2027 with YYYY-MM-DD is read as 1 April, a mismatched dateFormat moves with the time zone, until with excludes ends after the target, 16 hours from Friday afternoon ends on Saturday, and a duplicate id makes after use the last task. Ignored: excludes weekdays, tickInterval 1decade, and click call in strict mode.

The nine errors are the friendly ones: you see them. The other 17 are why a gantt chart should be read, not only rendered. The fixes for the silent ones:

You writeWhat you getWrite instead
Deploy: prod :2027-01-04, 1dA task called “Deploy”Deploy#58; prod :2027-01-04, 1d
Fix #42; hotfix :…A task called “Fix * hotfix”Fix #35;42 hotfix :… or Fix 42
Spec :2027-01-04, 3d %% noteZero lengthThe comment on its own line
3dX, 3 d, 3DZero length3d (lower case, no space)
title page :…, section review :…The title or a new sectionTitle page :…: any word that is not a keyword
click tracking :…, gantt review :…Parse errorRename the task
04.01.2027 with dateFormat YYYY-MM-DD1 April 2027A date that matches dateFormat
dateFormat DD-MM-YYYY with 2027-01-04Right in UTC, Sun 3 Jan 19:00 in New YorkA date that matches dateFormat
Two tasks with the id a1after a1 follows the secondUnique ids

The #42; trap comes from Mermaid’s entity syntax: # + a number + ; is a character code, and 42 is *. The date traps come from a fallback in the code: when a date does not match dateFormat, Mermaid hands it to the browser’s new Date(). 2027-01-04 is then read as midnight UTC, which is the evening before in New York, and 04.01.2027 is read the American way, as April.

Two more facts from the same run. A duration of 1d is a calendar day, not 24 hours: in Berlin, a task from 27 March 2027 12:00 with 1d ended at 12:00 the next day (23 hours, the clocks go forward), with 24h at 13:00. And a chart with only dateFormat HH:mm and times puts them on today’s date, which is fine for a deployment runbook and wrong for a plan.

When a timeline fits better

A gantt computes dates. Sometimes you only want events in order: a product history, the steps of an incident. That is Mermaid’s timeline (9.4.0+):

timeline
  title Product history
  section 2025
    Q1 : Idea : First users
    Q3 : Beta
  section 2026
    Q1 : 1.0 launch
    Q4 : 2.0 : Dark mode
A Mermaid 12.3.0 timeline titled Product history. Two section headers, 2025 in purple and 2026 in yellow, sit above four periods: Q1 and Q3 in purple, Q1 and Q4 in yellow. A horizontal arrow runs left to right under the periods. Under each period hang its events: Idea and First users under 2025 Q1, Beta under Q3, 1.0 launch under 2026 Q1, and 2.0 and Dark mode under Q4.

Each line is period : event : event. The period is plain text, so Mermaid never checks or sorts it: 2027-01-04, Q1 and Sprint 12 are all just labels, drawn in the order you write them.

Timeline lineResult
timeline TDTop to bottom (11.14.0+; LR is the default)
timeline BTNo error: “BT” becomes the first period
09:12 : Alert firesParse error: the first colon ends the period
"09:12" : Alert firesParse error as well
09#58;12 : Alert firesWorks, shows 09:12
A second line : Q2 GA under a periodAnother event for the same period
click … or classDef …Parse error: timelines have neither
14 periods without sectionsOne row, scaled down to fit the width

Colours work like a mind map’s: cScale0 to cScale11 (with cScaleLabel0… for the text) colour the sections, or each period when there are no sections, and timeline: disableMulticolor: true makes them all one colour. The mindmap post has the same theme recipe in more detail.

Pick a gantt when tasks have lengths and depend on each other. Pick a timeline when the story is the order of events, and when you want no date maths at all.

Where it renders

  • GitHub draws mermaid code blocks in issues, pull requests, discussions, wikis and Markdown files. It does not print its Mermaid version. Its docs suggest a block with the single word info, which renders the version (“v12.3.0” in our 12.3.0 test). Check it before you use vert (11.7.0+), multiple excludes lines (11.16.0+) or timeline TD (11.14.0+).
  • GitLab: the docs say “GitLab supports Mermaid version 12”, and GitLab’s package.json on 14 November 2026 pinned [email protected]. We tested 12.1.0 and 12.3.0, on either side of it, with the same dates.
  • Jira and Confluence show a mermaid block as code. You need an app; see the next section and our guide to Mermaid in Jira.
  • Obsidian, VS Code previews, Notion: each bundles its own Mermaid version, which we did not test. The info block works there too.

Gantt charts in Jira and Confluence

  • Mermaid Tabs for Jira draws a Mermaid diagram in a tab on the work item, Gantt and timeline included, with Mermaid 12. Every snippet on this page gave the same dates in 12.1.0 and 12.3.0, so it draws the same chart there. A plan in a tab sits next to the work it plans. The dates are text you type: the chart does not follow the work item’s own dates.
  • Excalidraw for Confluence puts a Mermaid Gantt on a whiteboard and keeps the text, so you can change the plan later and draw notes and arrows around it. Whether Insert as shapes splits it into shapes depends on the converter library: in our test, mermaid-to-excalidraw 2.2.2 returned one picture for the plan, and 3.6.0 (the newest) returned rectangles, lines, a diamond and text.

For the other Mermaid diagram types, see the flowchart guide, the mind map guide and the UML cheat sheet. If you want a gantt view that follows real work items rather than text, that is Jira’s own timeline; which Gantt features it has and lacks is in our Jira Gantt chart scorecard.

Sources

  • Mermaid gantt and timeline docs at the tested tag: gantt.md and timeline.md, [email protected].
  • The gantt grammar gantt.jison, ganttDb.js (date fallback, unknown ids, the 10,000 limit, enableTopAxis) and ganttRenderer.js at the same tag, in mermaid-js/mermaid.
  • Mermaid release notes: timeline in 9.4.0, until in 10.9.0, weekend in 11.0.0, vert in 11.7.0, timeline direction in 11.14.0, the excludes limit in 11.15.0, several excludes lines in 11.16.0, unknown-id warnings in 12.1.0.
  • Mermaid issues #5867 (until with excludes) and #7062 (hours over a weekend).
  • GitHub docs, Creating diagrams; GitLab docs, Mermaid, and GitLab’s package.json.
  • mermaid-to-excalidraw 2.2.2 and 3.6.0.
  • Our renders: 92 gantt and 23 timeline cases in Mermaid 11.17.2, 12.1.0 and 12.3.0, then 17 follow-up cases, every code block of this page, headless Chromium, time zone UTC (Berlin and New York for the time zone cases), 14 November 2026, no network requests during the run. Dates read from Mermaid’s own task list after parsing.

Frequently asked questions

How do I change the colors of a Mermaid gantt chart?

Use the base theme and set theme variables in the front matter: taskBkgColor and taskBorderColor for normal tasks, activeTaskBkgColor for active, doneTaskBkgColor for done and critBkgColor and critBorderColor for crit. For one task, add themeCSS with a selector on the end of its id, [id$="-spec"] { fill: #A78BFA; }, because Mermaid prefixes every id. A crit milestone or crit vertical marker turns red.

How do I exclude weekends in a Mermaid gantt chart?

Add the line excludes weekends. Durations then count working days only: a 5d task that starts on a Monday stops at the end of Friday, and the next task starts on Monday. Add weekend friday for a Friday and Saturday weekend, specific dates such as excludes 2027-12-24 for holidays, and includes for a Saturday you work. Hours are not moved past a weekend, and fixed end dates are not extended.

How do I add a milestone to a Mermaid gantt chart?

Write the tag first, then an id, a date or after, and a duration: Release :milestone, rel, after qa, 0d. The duration is required. Without it Mermaid stops with Invalid date:rel, because it reads the id as the start date. The diamond sits at the start date plus half the duration, so use 0d to put it exactly on the day.

Should I use a gantt or a timeline in Mermaid?

Use a gantt when tasks have dates and lengths and depend on each other: Mermaid computes the dates for you. Use a timeline for a story of events in order, such as a product history or an incident log: the periods are plain text, so Mermaid does not compute or check any date.

Why does my Mermaid gantt task start today?

Because an after or until points at an id Mermaid cannot find, so it falls back to today. Common causes are a typo, an id that contains a dot (spec.1 is read as spec), or a task without an id. Since Mermaid 12.1.0 the browser console shows a warning that names the unknown id, but the chart still draws.

Can ChatGPT write a Mermaid gantt chart?

Yes, and it usually gets the shape right. Check four things before you trust the dates: status tags come before the id, every milestone has a duration, no task name starts with a keyword such as title, section or click, and no comment sits at the end of a task line. Each of these breaks a chart in a way the 26 tests on this page show.

Keep reading