Skip to content
FluxGantt

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

LibraryJS (gzip)CSS (gzip)Total (gzip)
dhtmlx-gantt@10.0.3 (Community, MIT)176.53 KiB49.65 KiB226.18 KiB
frappe-gantt@1.2.213.15 KiB1.74 KiB14.89 KiB
Bryntum GanttNot independently measurable — see below
FluxGantt — createGantt() only7.35 KiB07.35 KiB
FluxGantt — + withRender14.49 KiB014.49 KiB
FluxGantt — + withRender + withInteraction19.16 KiB019.16 KiB
FluxGantt — every capability composed23.85 KiB023.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".

FluxGanttdhtmlx CommunityFrappe
Written inTypeScriptJavaScriptJavaScript
Type definitionsSource of truthHand-maintained .d.tsNone shipped
any in the public surfaceNone167 occurrences in dhtmlxgantt.d.tsn/a
ID safetyBranded TaskId / ResourceId — never interchangeablestring | numberuntyped
Strict null checksEnforced 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 PROBryntumFrappe (MIT)
Dependency typesFS / SS / FF / SF + lag/leadall fourall fourall fourFS 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 AApartialpartial✅❌
Touch-adapted layout✅ withResponsive()partial✅✅❌ (touchstart is wired for double-tap only)
ExportJSON / CSV / SVG / PNGlimited+ PDF, MS Project+ PDF, MS Project❌
Resource view + levelingPro (not yet shipped)❌✅✅❌
BaselinesPro (not yet shipped)❌✅✅❌
MS Project XML I/OPro (not yet shipped)✅✅✅❌
Price (list, 2026-11)$0, MIT$0, MITfrom $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 editionPaid entry priceModelSaaS use
FluxGanttMIT, includes critical pathPro: about $600–800 per developer (planned, not yet on sale)Perpetual, 12 months of updates, optional renewalAllowed in MIT; the Pro licence will allow it with no OEM surcharge
dhtmlx GanttMIT Community since v10 (2026-06-11), no critical pathPRO from $799 (1 dev, 1 project)Perpetual + 1 year of updates"Upon request for a fee"
Bryntum GanttMIT 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 yearPerpetual licence, support renews at the original priceRequires an OEM licence (quoted)
SVAR GanttMIT core, no critical pathPRO from $749 (1 dev)Perpetual + 1 year, renewal 70–80%Not in the $749 tier; from the $1,899 Application tier
Syncfusion GanttCommunity License (under $1M revenue, up to 5 devs)$959 per dev per yearSubscription—

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 a dependencies array of { from, to, type: 'FS' }, and its YYYY-MM-DD strings 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's TaskInput shape.

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 versions

FluxGantt's budgets are enforced on every pull request, so the numbers in the first table cannot silently drift upward: exceeding a budget fails CI.