Mermaid Gantt Charts: Syntax, Gotchas, and Round-Tripping to a Real Editor
Mermaid gantt blocks render natively in GitHub, GitLab, Notion and Obsidian, which makes them the easiest way to put a schedule where the work already lives — in the repo, reviewable in a pull request. They are also miserable to edit: move one date and you re-derive every after chain downstream by hand. This covers the syntax field by field, a worked example you can paste into a README, the traps that render perfectly while being wrong, and the missing step — editing visually and getting the text back.
The syntax in one pass
A gantt block opens with the keyword gantt and a few header lines, then section headings with task lines under them. Indentation is convention, not grammar.
A task line is a name, a colon, then comma-separated fields:
Task name :tag, id, start, duration
- Tags — any of
done,active,crit,milestone, in any order, and you can stack several. Optional. - id — a bare word, needed only if something else refers to this task by name.
- start — a date, or
after someId, or omitted entirely to continue from the task above. - duration —
5d,2w, or a second absolute date.
Header lines worth knowing: dateFormat (how dates in your file are written), excludes weekends, title, and axisFormat (how the axis is labelled, in strftime-style codes). Fields are classified by shape rather than strictly by position, which is why :done, res, 2026-01-05, 5d and :res, done, 2026-01-05, 5d both work.
A worked example you can paste into a README
A complete, valid block for real work — migrating a public API from v1 to v2. It uses sections, absolute dates, after chains, every tag, a milestone and excluded weekends. Paste it into a fenced mermaid block in any GitHub Markdown file and it renders.
gantt
title API v2 Migration
dateFormat YYYY-MM-DD
axisFormat %b %d
excludes weekends
section Discovery
Audit v1 endpoints :done, audit, 2026-03-02, 5d
Draft OpenAPI spec :done, spec, after audit, 4d
Spec review :active, review, after spec, 2d
section Build
Auth service :crit, auth, after review, 10d
Resource endpoints :res, after auth, 12d
Client SDK regen :sdk, after res, 3d
section Cutover
Staging soak :soak, after sdk, 5d
Public beta :milestone, beta, 2026-05-04, 0d
Deprecate v1 :dep, after beta, 2w
Reading it line by line:
dateFormat YYYY-MM-DDtells Mermaid how to read the dates you typed. Input format, not output — changing it does not change the axis.axisFormat %b %dis the output side: the axis reads "Mar 02" rather than a full ISO date. Use%Vfor week numbers on anything longer than a quarter.excludes weekendsmakes every bar step over Saturday and Sunday, for the whole diagram — there is no per-task override.Audit v1 endpoints :done, audit, 2026-03-02, 5d— tag, id, an absolute start (a Monday), five days. Durations are inclusive of the start day, so this ends Friday 6 March.after auditmeans "start whenauditfinishes" — with weekends excluded, Monday 9 March, not Saturday the 7th.:crit, auth, ...paints the bar in the critical-path colour. Note paints — see the limits below.Resource endpoints :res, after auth, 12dhas no tag at all; the first field is just an id. Untagged means upcoming work.Public beta :milestone, beta, 2026-05-04, 0dis a zero-length marker on a fixed date. Milestones get an id like anything else, soafter betaon the last line is legal.2wis two weeks. Mermaid also acceptshandm, rarely useful here.
The blank lines between sections are decorative — Mermaid ignores them, and so does our importer. Keep them anyway; a 40-task block without them is unreadable in a diff.
Task-line reference
Every field and modifier, what it does, and what it looks like in practice.
| Field or modifier | Example | What it does |
|---|---|---|
id | audit | A bare word naming the task so after can refer to it. No spaces or punctuation. Optional unless something depends on the task. |
after | after audit | Starts when the named task finishes. FS only, no lag. Accepts several ids — after a b waits for the later. |
done | :done, audit, … | Renders the bar as completed. No percentage — 100% and "basically finished" look identical. |
active | :active, review, … | Renders the bar as in progress. Again with no number attached. |
crit | :crit, auth, … | Colours the bar as critical. An assertion you type, not something Mermaid derives — nothing checks it against the dependency chain. |
milestone | :milestone, beta, … | Draws a diamond instead of a bar. Pair it with 0d. |
| Duration units | 5d · 2w · 8h | Days, weeks, hours (also m). Inclusive of the start day: 5d from Monday ends Friday. |
| End date | 2026-03-02, 2026-03-06 | A second date instead of a duration, for an externally fixed finish. |
dateFormat | dateFormat YYYY-MM-DD | How dates in the file are parsed. Header line, once per diagram. |
axisFormat | axisFormat %b %d | How the axis is labelled, in strftime codes. Purely cosmetic. |
excludes | excludes weekends | Non-working days. Also takes specific dates (excludes 2026-04-06) and weekday names. Whole diagram. |
Four things that will bite you
1. Durations are inclusive of the start day. 5d from Monday the 5th runs to Friday the 9th, not the 10th. An off-by-one here shifts every task in the file and still renders perfectly, which is the worst possible failure mode: nothing looks broken.
2. after plus excludes weekends is where the real bugs live. If a predecessor ends on a Friday, its successor starts on the Monday — not Saturday. Any tool that resolves after by adding one calendar day will quietly put tasks on weekends in a file that explicitly forbids them, and every downstream date drifts from there. Ours did this, briefly. The fix routes the arithmetic through the working calendar so the import agrees with what Mermaid draws, and the test guarding it asserts the property rather than specific dates: no derived start or end may land on an excluded day. Dates you typed by hand are left where you put them, weekend or not — silently moving an author's explicit date is the wrong kind of helpful.
3. There is no escaping. A colon starts the field list and a comma separates fields, so Phase 2: design, review becomes a task called "Phase 2" with garbage fields. Keep colons, commas and semicolons out of names; on export we substitute spaces rather than emit a line that will not parse.
4. An unparseable duration silently becomes zero. Write 3dd and you get a zero-length bar rather than an error. Scan for invisible tasks after a bulk edit.
The limits of a diagram format
Mermaid gantt is a rendering language, not a scheduling engine, and the difference shows up the moment you want the chart to answer a question rather than illustrate an answer.
- No resources. No field for who does the work, no cost, no effort, no units. You cannot over-allocate someone in Mermaid because Mermaid does not know anyone exists.
- No float, and no computed critical path.
critis a colour you apply by hand. Nothing walks the dependency graph, computes early and late dates, or tells you which chain drives the finish date. A diagram where every bar is taggedcritis as valid as one where none are. - No baseline. Nowhere to record what the plan said last month, so there is no variance to show and no slip to measure.
- Finish-to-start only.
afteris FS with zero lag. SS, FF, SF and any lag or lead have nowhere to go. Real plans are full of "start testing three days after development starts" — in Mermaid that becomes a hard-coded date, and the link is gone. - No progress percentage. A task at 40% and one at 90% are both just "active".
- Flat sections. No nested groups, so a WBS deeper than one level flattens on the way in.
None of that makes it a bad format. It makes it a publishing format — good for showing a schedule, useless for deriving one. Which is why the round trip matters.
Editing visually, then pasting the text back
Plenty of tools render Mermaid. What has been missing is the other direction — dragging bars and getting the syntax back out.
- Paste or open your diagram in gantts.app — a
.mmd, or a.mdwith a fenced block. It detects a gantt block by content, not extension. - Drag, link and re-date it like any other chart.
excludes weekendsswitches the working calendar on, so the dates it produces agree with the file it came from. - Export ▸ Mermaid gantt, copy, paste back into your README, commit the diff.
The trip is lossy in a known, boring way. Progress maps 100% to done and 1–99% to active on the way out; active comes back in as 50% — a guess you are told about in a warning rather than left to find in a status report. Links that cannot be written as after — anything with a lag, or any SS/FF/SF relationship — fall back to absolute dates, which stay correct even though they stop being maintainable.
One deliberate asymmetry, because it is not a bug: crit is exported but never imported. On the way out it is derived — the editor computed the critical path from the dependency graph, so the tag is true when written. On the way in it is a word somebody typed in a file that may be weeks stale, and trusting it would let an old diagram paint a non-critical chain red. So it is written and then ignored: the critical path you see after an import was recalculated, not asserted.
A useful side effect if you draft schedules with an LLM: ask for Mermaid gantt syntax, paste the answer in, and you have an editable chart with a computed critical path — no API key, no backend.
Templates that use this
Keep reading
- Gantt Chart Dependencies Explained: FS, SS, FF and SF
- Critical Path Method (CPM): Steps, Formula & Example
- What Is a Gantt Chart? Definition, Examples & Uses
Frequently asked questions
How do I write a Gantt chart in Mermaid?
Start the block with gantt, add dateFormat YYYY-MM-DD, then section headings with task lines under them in the form "Name :tag, id, start, duration" — for example "Research :done, res, 2026-01-05, 5d".
Does 5d in Mermaid include the start day?
Yes. A 5d task starting Monday the 5th finishes Friday the 9th. This inclusive counting is the most common source of off-by-one errors, and it produces a diagram that renders perfectly while every date is wrong by one day per task.
How do dependencies work in Mermaid gantt?
Use "after someId" as the start field. It is always finish-to-start with no lag — start-to-start, finish-to-finish and lags cannot be expressed. You can name several predecessors, as in "after a b", and the task waits for the later of them.
Does after skip weekends?
It does when the diagram declares "excludes weekends". A successor to a task ending on a Friday starts on the Monday, and its duration is counted in working days. Tools that resolve after by adding one calendar day put tasks on Saturdays in a file that forbids them.
Can Mermaid calculate the critical path?
No. The crit tag is a colour you apply by hand; nothing in Mermaid walks the dependency graph or computes float. That is why gantts.app exports crit but ignores it on import — criticality is recalculated from the dependencies rather than trusted from a possibly stale file.
Can I convert a Mermaid gantt chart into an editable chart?
Yes. Open the .mmd or the Markdown file in gantts.app, edit it visually, then use Export ▸ Mermaid gantt to copy the updated syntax back out.