flowchart-sequence-designer

May 21, 2026 ยท View on GitHub

npm version CI CodeQL

A TypeScript-first Bun/npm package for building and editing flowchart and sequence diagrams โ€” both programmatically via a fluent API and visually via a React drag-and-drop canvas editor.

๐Ÿ”— Live demo & developer docs โ†’ Open it to drive the editor, switch variants (Flowchart / Question / Journey / Sequence), and copy the same API snippets shown below straight from the docs tab. Every variant boots with a working sample diagram so you can poke without any setup.

Contents

Quick start

import { DiagramEditor } from 'flowchart-sequence-designer/ui';

export default function App() {
  return <DiagramEditor height={520} onChange={(m) => console.log(m)} />;
}

That's it โ€” no provider, no theme setup, no required props. The editor mounts with a sample diagram, a working toolbar, undo/redo, drag-to-pan, scroll-to-zoom, and export buttons for Mermaid / PlantUML / JSON / SVG / PNG. Pass theme="dark" or themeOverrides={โ€ฆ} to brand-match, or initialModel={emptyModel('flowchart')} to start blank.

Install

bun add flowchart-sequence-designer
# or
npm install flowchart-sequence-designer

React 18+ is a peer dependency for the UI components. The core API has zero runtime dependencies.


Programmatic API

Flowchart

import { flowchart } from 'flowchart-sequence-designer';

const diagram = flowchart('Order Flow')
  .node('start',   'Start',           { shape: 'circle' })
  .node('check',   'Payment valid?',  { shape: 'diamond' })
  .node('success', 'Confirm order',   { shape: 'rectangle' })
  .node('fail',    'Reject',          { shape: 'rectangle' })
  .edge('start',   'check')
  .edge('check',   'success', { label: 'Yes' })
  .edge('check',   'fail',    { label: 'No' });

console.log(diagram.toMermaid());

Node shapes

ShapeDescription
rectangleStandard process box (default)
diamondDecision / branch
circleStart or end terminal
parallelogramInput / output

Edge options

.edge(from, to, {
  label?: string,
  style?: 'solid' | 'dashed' | 'dotted',
  arrowhead?: 'arrow' | 'open' | 'none',
})

Sequence diagram

import { sequence } from 'flowchart-sequence-designer';

const diagram = sequence('Auth Flow')
  .actor('User')
  .actor('Server')
  .message('User',   'Server', 'POST /login')
  .message('Server', 'User',   '200 OK + token', { style: 'dashed' });

console.log(diagram.toMermaid());

Actors auto-register from message() calls, so you can skip .actor() if you prefer.


Export formats

Every builder exposes the same export methods:

diagram.toMermaid()   // string
diagram.toPlantUML()  // string
diagram.toJSON()      // string (serialised DiagramModel)
diagram.toSVG()       // string (SVG markup)
diagram.toPNG()       // Promise<Blob>  (browser only)

Import

import { fromMermaid, fromJSON } from 'flowchart-sequence-designer';

const model = fromMermaid('graph TD; A-->B; B-->C');
const model2 = fromJSON(jsonString);

Round-trip fidelity: fromMermaid(diagram.toMermaid()) produces an equivalent model.


Exporter / importer round-trip rules

The five export formats trade fidelity for portability. Use this table to pick the one that matches what you need:

FormatRound-tripPreservedDropped or lossy
JSONโœ… fullevery field โ€” variant, metadata, waypoint, x/y positions, edge arrowheads, message ordernothing
Mermaid (flowchart)partialnode shapes ([] {} (()) [/]), labels, edge connectors (-->, -.->, ---, -.-), edge labels, subgraph โ†’ metadata.grouppositions, waypoint, metadata.answers, variant. Dotted edges collapse to dashed.
Mermaid (sequence)partialactor order, message arrows (->>, -->>), labelsmessage metadata, styling overrides
PlantUML (flowchart)export-onlyedge styles (--> / -[dashed]-> / -[dotted]->), labels, node idshape distinctions (PlantUML state-diagram syntax is coarser), positions, metadata, variant
PlantUML (sequence)export-onlyactor order, message style (->, -->), labelsโ€“
SVGexport-only (rendered)full visual parity with the canvas โ€” same dot grid, same edge curves, same node stylingโ€“
PNGexport-only (rendered, browser-only)same as SVG, rasterized at devicePixelRatioโ€“

If you need 100% round-trip fidelity, use JSON. If you need a format that GitHub renders inline in markdown, use Mermaid. If you need a polished image for documentation, use SVG or PNG.


Presets

import {
  presetFlowchartModel,
  presetSequenceModel,
  emptyModel,
} from 'flowchart-sequence-designer/ui';

presetFlowchartModel('flowchart')  // 6-node order flow with one decision
presetFlowchartModel('question')   // 1-question / 3-answer router
presetFlowchartModel('journey')    // 5-step onboarding sequence
presetSequenceModel()              // 3-actor login handshake

emptyModel('flowchart')            // { type:'flowchart', variant:'flowchart', nodes:[], edges:[] }
emptyModel('flowchart', 'journey') // same with variant: 'journey'
emptyModel('sequence')             // { type:'sequence', nodes:[], edges:[], actors:[], messages:[] }

All presets return a deep clone โ€” mutate the result freely.


Working with the model directly

import { Model } from 'flowchart-sequence-designer';

const m = new Model('flowchart');         // new Model(type, title?, variant?)
m.addNode({ id: 'a', label: 'Step A', shape: 'rectangle' });
m.addNode({ id: 'b', label: 'Step B', shape: 'rectangle' });
m.addEdge({ id: 'e1', from: 'a', to: 'b', label: 'next' });

// Rehydrate from a saved DiagramModel:
// const m2 = Model.fromData(savedJson);

React UI component

Import from the /ui sub-entry to keep React out of the bundle for non-UI consumers:

import { DiagramEditor } from 'flowchart-sequence-designer/ui';

Basic usage

<DiagramEditor />

Mounted without an initialModel the editor boots with a small working sample diagram for the chosen variant โ€” a 6-node order-flow for flowchart, a role-picker for question, a 5-step onboarding for journey, and a 3-actor login handshake for the SequenceEditor. This gives anyone evaluating the package something to interact with from the first render. To start blank instead:

import { DiagramEditor, emptyModel } from 'flowchart-sequence-designer/ui';

<DiagramEditor initialModel={emptyModel('flowchart')} />

The presets are also exported in case you want to hydrate them from your own code: presetFlowchartModel(variant?) and presetSequenceModel().

All props

<DiagramEditor
  initialModel={model}          // pre-load a DiagramModel
  onChange={(m) => save(m)}     // fires on every node/edge change
  onExport={(fmt, content) => โ€ฆ} // intercept exports instead of auto-downloading
  height="100%"                 // any CSS height (default: 600)
  variant="flowchart"           // 'flowchart' | 'question' | 'journey'
  theme="auto"                  // 'light' | 'dark' | 'auto'
  allowedExports={['json','svg']} // restrict visible export buttons
  allowImport={true}            // show/hide the Import button
  themeOverrides={{             // optional per-color overrides
    canvas: '#0b0f1a',
    nodeSelectedFill: '#1f2a44',
  }}
/>

Diagram variants

VariantDescription
flowchartGeneral purpose โ€” any shapes, freeform connections
questionEach node is a question with lettered answer options (A, B, Cโ€ฆ). Each answer has its own connection port.
journeyNumbered milestone steps โ€” user path or process walkthrough

Editor features

Canvas

  • Drag nodes to reposition (snaps to 24px grid)
  • Scroll to zoom in/out (pinch to zoom on touch)
  • Drag the canvas background to pan (one-finger pan on touch)
  • Double-click a node to rename it inline
  • Dashed alignment guides appear when a dragged node lines up with a sibling's edge or center, and it snaps within 4 px
  • Bottom-right minimap โ€” click or drag to pan the viewport
  • Accessibility: every node, port, and control is keyboard-reachable with a visible focus ring; selection / add / delete actions announce via an aria-live status region; the edge-flow animation honours prefers-reduced-motion

Connecting nodes

  • Hover a node to reveal the bottom port dot, then drag it to another node
  • Question variant: each answer row has its own port dot โ€” drag it to route that answer to a specific node

Node Navigator (left panel)

  • Lists all nodes with shape badge, label, and connection counts
  • Search/filter by name
  • Click any row to jump to that node and center the canvas on it
  • Collapses to a slim icon strip

Step Editor (right panel)

  • Appears when a node is selected
  • Edit the node name, change its shape
  • Manage branches / answer options (add, remove, reorder)
  • Question variant shows connection status per answer

Context menu (right-click)

  • On canvas: Add node at cursor, Re-center, Undo, Redo
  • On node: Rename, Duplicate, Disconnect all edges, Delete
  • On edge: Style (solid/dashed/dotted), Arrowhead, Reset routing, Delete
  • On touch devices: long-press the canvas (~550ms) opens the canvas menu

Keyboard shortcuts

ShortcutAction
Ctrl+ZUndo
Ctrl+Y / Ctrl+Shift+ZRedo
Ctrl+0Fit all nodes in view
Ctrl+C / Ctrl+VCopy and paste the current selection (internal edges preserved, +24 px offset on paste)
Ctrl+DDuplicate the current selection
Delete / BackspaceRemove the current selection
EscapeDeselect, cancel in-flight edge drag, close context menu
Arrow keysNudge selection by 1 grid unit (Shift = 4 units)
Alt+ArrowTraverse to the nearest node in that direction from the current selection
Shift+clickToggle a node in/out of the current selection
Shift+drag (empty canvas)Box-select โ€” add every intersected node to the selection
Double-click edge labelRename the edge label inline
Drag edge midpointRoute the edge through a waypoint (right-click โ†’ Reset routing to clear)

Export / Import

  • Toolbar exports to Mermaid, PlantUML, JSON, SVG, PNG
  • Import accepts Mermaid syntax or JSON

Theming

<DiagramEditor theme="dark" />    // force dark
<DiagramEditor theme="light" />   // force light
<DiagramEditor theme="auto" />    // follows system prefers-color-scheme (default)

To match the editor to a host application's brand, pass themeOverrides โ€” a Partial<ThemeColors> that is shallow-merged on top of the resolved light/dark palette:

import { DiagramEditor, type ThemeColors } from 'flowchart-sequence-designer/ui';

const brand: Partial<ThemeColors> = {
  canvas: '#0b1020',
  nodeFill: '#111a2e',
  nodeStroke: '#2b3a5a',
  nodeSelectedFill: '#1a2447',
  edgeColor: '#7b8aa6',
  textPrimary: '#e6edf7',
};

<DiagramEditor theme="dark" themeOverrides={brand} />;

Every field on ThemeColors (canvas, nodeFill, nodeStroke, edgeColor, panelBg, inputBg, โ€ฆ) is overridable. Sequence diagrams accept the same prop with a slightly different shape โ€” Partial<SequenceThemeColors> โ€” also exported from flowchart-sequence-designer/ui.


SequenceEditor

<DiagramEditor> auto-delegates to <SequenceEditor> when handed a sequence model, but you can also mount it directly to avoid the type-check redirect:

import { SequenceEditor, presetSequenceModel } from 'flowchart-sequence-designer/ui';

<SequenceEditor
  initialModel={presetSequenceModel()}
  height={520}
  theme="dark"
  onChange={(m) => save(m)}
/>

SequenceEditor props (mirrors DiagramEditorProps minus variant):

PropTypeDefaultNotes
initialModelDiagramModelpresetMust have type: 'sequence'. Falls back to the preset if a non-sequence model is passed.
onChange(m: DiagramModel) => voidโ€“Fires on every committed mutation.
onExport(format, content) => voiddownloadReceives string for text formats, Blob for PNG.
heightnumber | string600Any CSS height.
allowedExportsExportFormat[]allWhitelist of toolbar export buttons.
allowImportbooleantrueShow the Import button.
theme'light' | 'dark' | 'auto''auto'auto follows OS prefers-color-scheme.
themeOverridesPartial<SequenceThemeColors>โ€“Per-property palette overrides.

Sequence-specific interactions:

  • Drag a message row by its handle to reorder messages.
  • Double-click a message label to rename inline.
  • Drag the column header of an actor to reorder lifelines.

Restricting exports and import

// Only allow JSON and SVG download
<DiagramEditor allowedExports={['json', 'svg']} />

// Hide the import button entirely
<DiagramEditor allowImport={false} />

// Handle exports yourself (e.g. send to an API)
<DiagramEditor
  onExport={(format, content) => {
    if (format === 'json') myApi.save(content as string);
  }}
/>

Framework Wrappers

FrameworkPackageDocs
Angular@flowchart-sequence-designer/angularDocs & Demo
Vue@flowchart-sequence-designer/vueDocs & Demo

Types

Core entry (flowchart-sequence-designer)

import type {
  DiagramModel,
  DiagramNode,
  DiagramEdge,
  DiagramVariant,
  DiagramType,
  NodeShape,
  ExportFormat,
  SequenceMessage,
  ValidationError,
} from 'flowchart-sequence-designer';

import {
  Model,                 // class โ€” build / query / validate diagrams
  flowchart,             // fluent FlowchartBuilder factory
  sequence,              // fluent SequenceBuilder factory
  toMermaid, fromMermaid,
  toPlantUML,
  toJSON, fromJSON,
  toSVG, toPNG,
} from 'flowchart-sequence-designer';

UI entry (flowchart-sequence-designer/ui)

import {
  DiagramEditor,         // flowchart / question / journey editor
  SequenceEditor,        // sequence diagram editor
  Toolbar,               // standalone toolbar (used internally)
  StepEditor,            // node property panel (used internally)
  presetFlowchartModel,  // starter model for flowchart variants
  presetSequenceModel,   // starter model for sequence
  emptyModel,            // blank model factory
} from 'flowchart-sequence-designer/ui';

import type {
  DiagramEditorProps,
  SequenceEditorProps,
  ThemeColors,           // flowchart theme palette
  SequenceThemeColors,   // sequence theme palette
} from 'flowchart-sequence-designer/ui';

DiagramModel

interface DiagramModel {
  type: 'flowchart' | 'sequence';
  variant?: DiagramVariant;    // 'flowchart' | 'question' | 'journey' (flowchart-type only)
  title?: string;
  nodes: DiagramNode[];        // always present (empty array for sequence models)
  edges: DiagramEdge[];        // always present (empty array for sequence models)
  actors?: string[];           // sequence models only โ€” ordered actor names
  messages?: SequenceMessage[]; // sequence models only โ€” ordered messages
}

DiagramNode

interface DiagramNode {
  id: string;
  label: string;
  shape?: 'rectangle' | 'diamond' | 'circle' | 'parallelogram';
  x?: number;
  y?: number;
  metadata?: Record<string, unknown>;
  // question variant: metadata.answers = string[]
}

DiagramEdge

interface DiagramEdge {
  id: string;
  from: string;
  to: string;
  label?: string;
  style?: 'solid' | 'dashed' | 'dotted';
  arrowhead?: 'arrow' | 'none' | 'open';
  waypoint?: { x: number; y: number }; // manual routing point (JSON only)
}

SequenceMessage

interface SequenceMessage {
  id: string;
  from: string;            // actor name
  to: string;              // actor name
  label: string;
  style?: 'solid' | 'dashed';
}

ValidationError

interface ValidationError {
  kind: 'dangling-from' | 'dangling-to' | 'duplicate-node-id' | 'duplicate-edge-id';
  id: string;
  message: string;
}

Package structure

flowchart-sequence-designer/
โ”œโ”€โ”€ dist/
โ”‚   โ”œโ”€โ”€ index.js / index.cjs / index.d.ts   โ† core (no React)
โ”‚   โ””โ”€โ”€ ui/
โ”‚       โ””โ”€โ”€ index.js / index.cjs / index.d.ts โ† React UI
โ””โ”€โ”€ src/
    โ”œโ”€โ”€ core/          # types, Model, FlowchartBuilder, SequenceBuilder
    โ”œโ”€โ”€ exporters/     # mermaid, plantuml, json, svg, png
    โ”œโ”€โ”€ importers/     # mermaid, json
    โ””โ”€โ”€ ui/            # DiagramEditor, SequenceEditor, Toolbar, hooks

The "." export gives you the core API; "./ui" gives you the React components. Consumers that only use the programmatic API never pull in React.


Security

This package takes security seriously:

  • Input sanitization โ€” All user-provided text is sanitized before rendering (HTML tags, javascript:/data:/vbscript: URIs, on* event handlers, and control characters are stripped). See src/core/sanitize.ts.
  • Resource limits โ€” Importers enforce hard caps (500 nodes, 2000 edges, 100 actors, 2000 messages, 2MB input) to prevent resource exhaustion.
  • Prototype pollution defense โ€” JSON importer strips __proto__, constructor, and prototype keys recursively.
  • SVG export โ€” Defence-in-depth: sanitize first, then XML-escape. Safe even if consumed by less-strict parsers.
  • No eval / innerHTML โ€” The codebase never uses dynamic code execution or raw HTML injection.
  • CodeQL โ€” Automated security scanning runs weekly and on every PR.
  • Dependabot โ€” Dependency updates monitored weekly.

To report a vulnerability, see SECURITY.md.


Troubleshooting

toPNG throws in Node / Bun server context

toPNG requires the browser Canvas API. Use toSVG on the server and pipe the output through @resvg/resvg-js:

import { toSVG } from 'flowchart-sequence-designer';
import { Resvg } from '@resvg/resvg-js';

const svg = toSVG(model);
const png = new Resvg(svg).render().asPng();

Peer dependency warning for React

The package lists react >= 17 as a peer dependency. If your project uses React 18 or 19 you may see a warning โ€” it is safe to ignore. All features are tested against React 19.

Canvas renders blank / empty

Ensure the height prop is set to a numeric value (height={600}) rather than "100%". A percentage height requires the parent element to have an explicit height, which browsers often don't enforce in flex containers.

Large diagrams feel sluggish

The canvas is SVG-based and renders every node each frame during drag. For diagrams with more than ~200 nodes, consider reducing the number of simultaneously visible nodes or splitting the diagram into sections. Node dimensions are memoized per render cycle, so layout recalculations are batched.

Mermaid import drops some features

Mermaid import is intentionally lossy โ€” it preserves shapes, edge styles, labels, titles, and subgraph grouping, but drops canvas positions, waypoints, per-edge metadata, and the diagram variant (question, journey). These fields have no Mermaid syntax equivalent. Use JSON export/import for a full lossless round-trip.


Building from source

bun install
bun run build        # outputs to dist/
bun test             # 105 tests
bun run typecheck    # tsc --noEmit
bun run lint         # eslint
bun run format:check # prettier --check