FluxGantt vs dhtmlx, Bryntum, SVAR, Frappe
Every number about FluxGantt on this page comes from pnpm size in CI. Every number about
another library comes from
node tooling/scripts/measure-competitor-bundles.mjs, which downloads
the published npm tarball at a pinned version and gzips its own advertised entry point. Both are
in the main repo; both are re-runnable. If a figure here is stale, that script is how you prove it.
The short version
If you need resource leveling, baselines and MS Project round-tripping today, buy dhtmlx PRO, Bryntum or SVAR PRO. They are mature products and FluxGantt's Pro tier does not exist yet.
"MIT" alone no longer sets FluxGantt apart: dhtmlx Gantt 10, Bryntum 8 and SVAR all have MIT editions now. What none of those MIT editions includes is a critical path, and FluxGantt's does. Add a headless engine that runs in Node and Workers, a small tree-shakable bundle, WCAG 2.1 AA checked with axe in CI, and a licence that never charges extra for SaaS use. That combination is the gap FluxGantt is built for. The details are in Licences and pricing.
And if you are choosing purely on bundle size, read the Frappe row honestly: Frappe Gantt is smaller than a fully-interactive FluxGantt chart. It is smaller because it does considerably less. That trade is laid out below rather than hidden.
Bundle size
| Library | JS (gzip) | CSS (gzip) | Total (gzip) |
|---|---|---|---|
dhtmlx-gantt@10.0.3 (Community, MIT) | 176.53 KiB | 49.65 KiB | 226.18 KiB |
frappe-gantt@1.2.2 | 13.15 KiB | 1.74 KiB | 14.89 KiB |
| Bryntum Gantt | Not independently measurable — see below | ||
FluxGantt — createGantt() only | 7.35 KiB | 0 | 7.35 KiB |
FluxGantt — + withRender | 14.49 KiB | 0 | 14.49 KiB |
FluxGantt — + withRender + withInteraction | 19.16 KiB | 0 | 19.16 KiB |
| FluxGantt — every capability composed | 23.85 KiB | 0 | 23.85 KiB |
Three things about that table need saying out loud, because a benchmark that only flatters the thing being benchmarked is not a benchmark.
The CSS column is not padding. dhtmlx and Frappe both require you to import their stylesheet;
a chart that does not paint is not a comparison. FluxGantt requires zero host CSS — every
visual property is written inline with a --fg-* custom-property fallback, so the stylesheet you
add is only the one that overrides defaults (Theming). That is a real
architectural difference, not a measurement trick, and it is why the column reads 0.
The measurement method is asymmetric, in the competitors' favour. FluxGantt's numbers are
bundler output — tree-shaken, because the package is "sideEffects": false and each capability is
its own subpath export. The competitor numbers are their entry files measured whole, because
dhtmlx-gantt declares no sideEffects and frappe-gantt declares ["*.css"]: they are
monoliths, so a bundler cannot drop anything and the entry file is what ships. Tree-shaking
FluxGantt is not a favour to FluxGantt — it is what actually happens in your build, and it is the
only reason a row can read 7.35 KiB.
FluxGantt rows exclude the Temporal polyfill. Core reads globalThis.Temporal and bundles no polyfill. Runtimes without native Temporal (Node 22/24 and older browsers at the time of writing) need @js-temporal/polyfill, about 45.8 KiB gzip (@js-temporal/polyfill@0.5.1 with its jsbi dependency, minified). Add that to any FluxGantt row if your users are on such a runtime: an editable chart is then roughly 65 KiB, still well under dhtmlx. Runtimes with native Temporal pay nothing. Only the hello-world, editable and everything-composed rows are hard CI gates; the others are tracked report-only.
FluxGantt rows are measured as ESM output. pnpm size bundles each fixture with esbuild as
format: 'esm', because that is what a Vite, Rollup, webpack 5 or esbuild build of your app
consumes (the package's import condition). An IIFE wrapper would add esbuild's CJS-interop
helpers, about 740 raw bytes and up to ~320 B gzip on the larger rows, that no ESM bundler
emits. One known bias remains: size-limit subtracts a fixed 32 B calibration sized for an empty
IIFE, so ESM figures read about 12 B lower than the true bundle. The competitor rows are not
adjusted for any of this: they are whole published entry files gzipped at level 9, with no
bundler and no calibration, so the two methods are comparable in spirit but not byte-identical.
None of this changes any conclusion on this page (the nearest comparison, Frappe at 14.89 KiB
against an editable FluxGantt chart at 19.16 KiB, is a gap of several KiB).
Frappe is smaller, and that is fine. 14.89 KiB buys you finish-to-start dependencies drawn as arrows and drag-to-move. It does not buy you a critical path, other dependency types, lag/lead, a working calendar, or types. FluxGantt at 19.16 KiB buys all of those. The comparison worth making is not "who is smaller" but "what does each byte buy" — and the interesting row is the first one: FluxGantt's headless engine is 7.35 KiB with no renderer at all, which is the configuration a server-side scheduler or a test suite actually imports.
Why Bryntum has no number
@bryntum/gantt on npm is a 13 KB placeholder package. The real bundle is behind a paid,
login-gated download, so there is no artifact anyone can independently re-measure and no version
string to pin. Rather than quote a figure from a marketing page, this page leaves the cell empty.
Bryntum is a large, feature-complete, framework-integrated product; treat its size as "in the
same class as dhtmlx" and verify it yourself against your own build if it matters to you.
TypeScript
This is the other half of the pitch, and it is more than "ships a .d.ts".
| FluxGantt | dhtmlx Community | Frappe | |
|---|---|---|---|
| Written in | TypeScript | JavaScript | JavaScript |
| Type definitions | Source of truth | Hand-maintained .d.ts | None shipped |
any in the public surface | None | 167 occurrences in dhtmlxgantt.d.ts | n/a |
| ID safety | Branded TaskId / ResourceId — never interchangeable | string | number | untyped |
| Strict null checks | Enforced repo-wide | — | — |
The any count is grep -c ": any" codebase/dhtmlxgantt.d.ts on the tarball measured above. It
is not a criticism of the people who wrote it — hand-maintaining 7,757 lines of declarations for a
JavaScript codebase is how that number happens. It is the argument for generating types from
TypeScript source instead.
Branded IDs are the part you feel daily:
import { toTaskId, toResourceId } from '@fluxgantt/core';
const task = toTaskId('design');
const resource = toResourceId('alice');
gantt.linkTasks(task, resource, 'FS');
// ^^^^^^^^ Argument of type 'ResourceId' is not assignable to 'TaskId'In a string | number API that line compiles and fails at runtime, in production, on someone
else's data.
Features
Verified against the tarballs measured above, not against vendor feature grids. Where a cell says
❌ for Frappe it is because the capability is absent from its source, not merely undocumented — for
instance frappe-gantt@1.2.2 accepts no parent field on a task (so there is no hierarchy to roll
up), and its only touch listener is a double-tap detector in bar.js, not a drag path.
| FluxGantt (MIT) | dhtmlx Community (MIT) | dhtmlx PRO | Bryntum | Frappe (MIT) | |
|---|---|---|---|---|---|
| Dependency types | FS / SS / FF / SF + lag/lead | all four | all four | all four | FS only |
| Critical path | ✅ CPM | ❌ PRO only¹ | ✅ | ✅ | ❌ |
| Hierarchy + summary rollup | ✅ | ✅ | ✅ | ✅ | ❌ |
| Working calendar | ✅ Temporal, DST-correct | ✅ | ✅ | ✅ | ❌ |
| Headless / runs in Node | ✅ | ❌ DOM-coupled | ❌ | ❌ | ❌ |
| Framework-agnostic core | ✅ | ❌ | ❌ | ❌ | ✅ |
| React / Vue wrappers | ✅ first-party | ❌ | ✅ | ✅ | ❌ |
| Canvas fallback at scale | ✅ automatic ≥2000 tasks | ❌ | ❌ | ✅ | ❌ |
| Keyboard navigation + ARIA | ✅ treegrid, WCAG 2.1 AA | partial | partial | ✅ | ❌ |
| Touch-adapted layout | ✅ withResponsive() | partial | ✅ | ✅ | ❌ (touchstart is wired for double-tap only) |
| Export | JSON / CSV / SVG / PNG | limited | + PDF, MS Project | + PDF, MS Project | ❌ |
| Resource view + leveling | Pro (not yet shipped) | ❌ | ✅ | ✅ | ❌ |
| Baselines | Pro (not yet shipped) | ❌ | ✅ | ✅ | ❌ |
| MS Project XML I/O | Pro (not yet shipped) | ✅ | ✅ | ✅ | ❌ |
| Price (list, 2026-11) | $0, MIT | $0, MIT | from $799 perpetual² | $699 / $1,100 per dev per year² | $0, MIT |
¹ dhtmlx Gantt 10 moved the Community edition to MIT but keeps auto-scheduling, critical path and resource planning in PRO (DHTMLX Gantt 10.0, 2026-06). This cell previously read ✅. ² See Licences and pricing for tiers, SaaS terms and sources.
Licences and pricing (November 2026)
List prices read on 2026-11-11 and 2026-11-12. Vendors change these often, so check the linked page before you rely on a figure.
| Free edition | Paid entry price | Model | SaaS use | |
|---|---|---|---|---|
| FluxGantt | MIT, includes critical path | Pro: about $600–800 per developer (planned, not yet on sale) | Perpetual, 12 months of updates, optional renewal | Allowed in MIT; the Pro licence will allow it with no OEM surcharge |
| dhtmlx Gantt | MIT Community since v10 (2026-06-11), no critical path | PRO from $799 (1 dev, 1 project) | Perpetual + 1 year of updates | "Upon request for a fee" |
| Bryntum Gantt | MIT Community since 8.0.0 (2026-11-03); critical path, baselines, MS Project import and PDF export are Enterprise (changelog) | Business $699, Enterprise $1,100 per dev per year | Perpetual licence, support renews at the original price | Requires an OEM licence (quoted) |
| SVAR Gantt | MIT core, no critical path | PRO from $749 (1 dev) | Perpetual + 1 year, renewal 70–80% | Not in the $749 tier; from the $1,899 Application tier |
| Syncfusion Gantt | Community License (under $1M revenue, up to 5 devs) | $959 per dev per year | Subscription | — |
The pattern across the paid tiers: critical path and auto-scheduling sit behind the paywall, and using the chart inside a SaaS product costs extra. FluxGantt's MIT core includes the critical path, and its planned Pro licence is meant to remove the SaaS surcharge.
Where FluxGantt genuinely loses
- Maturity. dhtmlx has shipped for over a decade against thousands of production apps. FluxGantt is pre-1.0 (on npm since 2026-09).
- Pro-tier features do not exist yet. Resource leveling, baselines, constraints and MS Project XML are Wave 2. If you need them now, dhtmlx Community already ships MS Project I/O — for free.
- Frappe is smaller and simpler. If all you need is bars and FS arrows, it is less library to own.
- No Svelte or Angular wrapper yet. React and Vue only. Svelte and Angular are planned as part of the MIT core, not as paid add-ons. SVAR already ships React, Svelte and Vue.
Where the difference is architectural rather than a feature row
The headless split is the one competitors cannot close by adding a feature, because it is a
consequence of how the code is laid out. createGantt() has no DOM dependency at all, so the
scheduling engine runs in Node, in a Worker, or in a test with no jsdom:
import { createGantt } from '@fluxgantt/core'; // headless engine, no renderer
const gantt = createGantt({ tasks, dependencies });
const { criticalTaskIds, projectEnd } = gantt.computeCriticalPath();
// No document, no window, no mount. Runs on your server.Rendering, interaction, IO, theming and responsive behaviour are opt-in mixins on separate subpath exports, and that is structural rather than aspirational: class prototype methods can never be tree-shaken, which is why the facade was split (API surface). A consumer that only schedules never downloads a renderer.
Migrating
- From Frappe — the data shapes are close. Frappe's
dependencies: "a, b"string becomes adependenciesarray of{ from, to, type: 'FS' }, and itsYYYY-MM-DDstrings are accepted directly. See Tasks and Dependencies. - From dhtmlx — a dedicated migration guide is planned for Wave 2 alongside MS Project XML
import, which is the realistic bulk-migration path. Today, export from dhtmlx to JSON and map
its
{ id, text, start_date, duration, parent }onto FluxGantt'sTaskInputshape.
Reproducing every number here
git clone https://github.com/thai-pc/fluxgantt
cd fluxgantt && pnpm install && pnpm build
pnpm size # FluxGantt, all 7 fixtures
node tooling/scripts/measure-competitor-bundles.mjs # dhtmlx + Frappe, pinned versionsFluxGantt's budgets are enforced on every pull request, so the numbers in the first table cannot silently drift upward: exceeding a budget fails CI.