@immediate-diagram/react
v1.2.3
Published
React component library for [Immediate Diagram](https://github.com/immediate-diagram/immediate-diagram) — an LLM-native diagramming platform that transforms natural-language prompts into rich, stateful, animated SVG diagrams.
Readme
@immediate-diagram/react
React component library for Immediate Diagram — an LLM-native diagramming platform that transforms natural-language prompts into rich, stateful, animated SVG diagrams.
Render any of the 7 supported diagram types (flowchart, sequence, architecture, pie, donut, block, venn) from a concise text DSL — with theming, interactive zoom/pan, timeline playback, and accessibility built in.
Installation
npm install @immediate-diagram/react
# or
bun add @immediate-diagram/react
# or
yarn add @immediate-diagram/react
# or
pnpm add @immediate-diagram/reactPeer dependencies: React ≥ 18.0.0 and ReactDOM ≥ 18.0.0 must be installed in your project.
Quick Start
Render a diagram in 3 lines:
import { ImmediateDiagram } from "@immediate-diagram/react";
function App() {
return <ImmediateDiagram dsl="flowchart\n A --> B --> C" />;
}That's it — parsing, layout, rendering, and theming are handled automatically.
Table of Contents
- Components
- ImmediateDiagram — All-in-one (recommended)
- DiagramViewer — From parsed AST
- DiagramViewerFromSource — From DSL string
- InteractiveDiagramViewer — Zoom/pan/collapse
- PlaybackController — Timeline transport bar
- ZoomControls — Floating zoom buttons
- CopyImageButton — Copy diagram as PNG
- DiagramTooltip — Element tooltips
- ThemeToggle — Light/dark toggle
- Toast & ToastContainer — Notifications
- ConfirmDialog — Confirmation modal
- DiagramWrapper — Theme-aware container
- DiagramErrorBoundary — Error boundary
- RecoveryErrorPanel — Parse error display
- LoadingSpinner — Loading indicator
- Button — Styled button
- Context & Provider
- Hooks
- Utilities
- Theming
- Examples
- CSS (Optional)
- Migration from @immediate-diagram/web
- TypeScript Support
- Browser Support
- Troubleshooting
- API Reference
Components
ImmediateDiagram
The primary, all-in-one integration component. Takes raw DSL text and handles everything: parsing, layout, SVG rendering, theming, error display, and optional timeline playback.
This is the recommended starting point for most use cases.
import { ImmediateDiagram } from "@immediate-diagram/react";
<ImmediateDiagram
dsl={`flowchart
Start --> Process --> End`}
theme="dark"
interactive
showControls
onStateChange={(state) => console.log("State:", state)}
/>Props — ImmediateDiagramProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| dsl | string | (required) | Raw IMD DSL source string to parse and render. |
| theme | "light" \| "dark" | "light" | Theme for the diagram and playback controller. |
| width | number \| string | "100%" | Container width (number = px, string = CSS value). |
| height | number \| string | "auto" | Container height (number = px, string = CSS value). |
| animate | boolean | true | Enable CSS transitions for state changes. |
| showControls | boolean | undefined | Show playback controls. undefined = auto-detect (show if ≥ 2 states). |
| autoPlay | boolean | false | Auto-play the timeline on mount. |
| defaultTimeline | string | undefined | Name of the initial timeline to activate. |
| playbackSpeed | number | 1 | Initial playback speed multiplier. |
| interactive | boolean | false | Enable zoom/pan viewport (mouse wheel, drag, pinch). |
| showZoomControls | boolean | false | Show floating zoom control overlay (requires interactive). |
| initialZoom | "fit" \| "actual" | "fit" | Initial zoom: fit to container or 100% (requires interactive). |
| minScale | number | 0.1 | Minimum zoom scale (requires interactive). |
| maxScale | number | 10 | Maximum zoom scale (requires interactive). |
| sanitize | boolean | false | Sanitize SVG through DOMPurify before rendering. |
| className | string | — | Additional CSS class for the container. |
| style | CSSProperties | — | Additional inline styles for the container. |
| onStateChange | (state: string) => void | — | Called when the diagram transitions to a new state. |
| onNodeClick | (nodeId: string) => void | — | Called when a diagram node is clicked. |
| onTimelineEnd | () => void | — | Called when the timeline reaches the end. |
| onError | (error, errorInfo) => void | — | Error callback for rendering failures. |
| errorFallback | (error, reset) => ReactNode | — | Custom render error fallback. |
| renderError | (error, source) => ReactNode | — | Custom parse error renderer. |
| data-testid | string | "immediate-diagram" | Test ID for the container element. |
Imperative Ref — ImmediateDiagramRef
For programmatic playback control, use a ref:
import { useRef } from "react";
import { ImmediateDiagram, type ImmediateDiagramRef } from "@immediate-diagram/react";
function App() {
const ref = useRef<ImmediateDiagramRef>(null);
return (
<>
<ImmediateDiagram ref={ref} dsl={dsl} showControls />
<button onClick={() => ref.current?.play()}>Play</button>
<button onClick={() => ref.current?.goToState("peak")}>Go to "peak"</button>
</>
);
}| Method | Description |
|--------|-------------|
| play() | Start or resume playback. |
| pause() | Pause playback. |
| stepForward() | Advance to the next state. |
| stepBack() | Go back to the previous state. |
| goToState(name) | Jump to a named state. |
| setSpeed(speed) | Set playback speed multiplier. |
| setTimeline(name) | Switch to a different timeline. |
| getCurrentState() | Get the current state name. |
| getTimelines() | Get all available timeline names. |
Playback Controller Behavior
The playback controller adapts to the number of states:
- 0–1 states: No controller rendered.
- 2 states: Compact toggle button.
- 3+ states: Full controller with scrubber, speed, and loop controls.
DiagramViewer
Lower-level component that accepts a pre-parsed DiagramAST. Must be wrapped in a DiagramProvider.
Use this when you already have a parsed AST, need fine-grained control over layout/render options, or want to share a single DiagramProvider across multiple diagrams.
import { parse } from "@immediate-diagram/parser";
import { DiagramProvider, DiagramViewer } from "@immediate-diagram/react";
const ast = parse("flowchart\n A --> B");
function App() {
return (
<DiagramProvider>
<DiagramViewer ast={ast} />
</DiagramProvider>
);
}Props — DiagramViewerProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| ast | DiagramAST | (required) | Parsed diagram AST from the parser. |
| stableViewBox | BoundingBox | — | Fixed viewBox for state transitions (prevents layout shift). |
| debounceMs | number | — | Debounce delay for AST changes (ms). 100–300 recommended for live editing. |
| lazy | boolean | false | Enable IntersectionObserver-based lazy rendering. |
| lazyRootMargin | string | "0px" | Root margin for lazy IntersectionObserver. |
| sanitize | boolean | false | Sanitize SVG through DOMPurify. |
| tooltip | boolean | false | Enable interactive tooltips on diagram elements. |
| tooltipSelector | string | ".imd-arc, .imd-circle, .imd-node-group" | CSS selector for tooltip-triggering elements. |
| layoutOptions | object | — | Override layout engine defaults (maxWidth, padding, nodeSpacing, etc.). |
| renderOptions | Omit<RenderOptions, "theme"> | — | Override SVG render options (theme is always set from context). |
| className | string | — | Additional CSS class. |
| style | CSSProperties | — | Additional inline styles. |
| pageTheme | "light" \| "dark" | — | Page-level theme (auto-detected from document if omitted). |
| borderRadius | number | 8 | Wrapper border radius (px). |
| padding | number | 16 | Wrapper padding (px). |
| skeletonWidth | number | 600 | Skeleton placeholder width (px). |
| skeletonHeight | number | 400 | Skeleton placeholder height (px). |
| skeletonVariant | "shimmer" \| "pulse" \| "none" | auto | Skeleton animation variant. |
| onError | (error, errorInfo) => void | — | Error boundary callback. |
| errorFallback | (error, reset) => ReactNode | — | Custom error fallback. |
| aria-label | string | "Diagram" | Accessible label. |
| data-testid | string | "diagram-viewer" | Test ID. |
DiagramViewerFromSource
Mid-level component that accepts a DSL string, parses it with error recovery, and renders. Must be wrapped in a DiagramProvider.
Useful when you want the parse-and-render pipeline but need to control the provider separately (e.g., shared font loading, custom theme).
import { DiagramProvider, DiagramViewerFromSource } from "@immediate-diagram/react";
function Editor({ source }: { source: string }) {
return (
<DiagramProvider theme="dark">
<DiagramViewerFromSource source={source} debounceMs={200} />
</DiagramProvider>
);
}Props — DiagramViewerFromSourceProps
Extends most DiagramViewerProps (except ast), plus:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| source | string | (required) | Raw IMD DSL source string. |
| debounceMs | number | — | Debounce delay for source changes (ms). |
| renderParseError | (errors, source, hasPartial) => ReactNode | — | Custom parse error renderer. |
When parse errors occur, the component shows both the partial diagram (if recoverable) and a RecoveryErrorPanel with all errors and suggestions.
InteractiveDiagramViewer
Full interactive viewer with zoom/pan. Extends DiagramViewer with:
- Mouse wheel zoom (cursor-centered)
- Click-drag pan
- Double-click fit-to-view
- Touch pinch-zoom on mobile
- Keyboard shortcuts (arrow keys for pan, +/- for zoom, 0 to reset)
- Block collapse/expand for block diagrams (click block labels)
- Optional zoom controls overlay
- Optional copy-to-clipboard button
Must be wrapped in a DiagramProvider.
import { DiagramProvider, InteractiveDiagramViewer } from "@immediate-diagram/react";
<DiagramProvider>
<InteractiveDiagramViewer
ast={ast}
interactive
initialZoom="fit"
showZoomControls
showCopyButton
minScale={0.1}
maxScale={5}
/>
</DiagramProvider>Props — InteractiveDiagramViewerProps
Extends all DiagramViewerProps, plus:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| interactive | boolean | true | Enable zoom/pan interactivity. |
| initialZoom | "fit" \| "actual" | "fit" | Initial zoom behavior. |
| showZoomControls | boolean | false | Show floating zoom control overlay. |
| showCopyButton | boolean | false | Show "Copy as image" button overlay. |
| minScale | number | 0.1 | Minimum zoom scale. |
| maxScale | number | 10 | Maximum zoom scale. |
| onCopySuccess | () => void | — | Called after successful clipboard copy. |
| onFallbackDownload | () => void | — | Called when clipboard write falls back to download. |
| onCopyError | (message: string) => void | — | Called when copy/download fails. |
Block Collapse
For block diagrams, clicking a block label toggles collapse/expand. Collapsed blocks display as compact placeholder rectangles with a ▶ indicator and child count badge. This is implemented as a pure AST transformation before layout — the layout engine always receives a clean AST.
PlaybackController
Standalone playback transport bar for timeline-based diagrams. Wire it to the usePlaybackController hook.
import { PlaybackController, usePlaybackController, DiagramProvider } from "@immediate-diagram/react";
function TimelineViewer({ ast }) {
const controller = usePlaybackController({
timelines: ast.timelines,
transitions: ast.transitions,
});
return (
<DiagramProvider>
<DiagramViewer ast={applyState(ast, controller.currentStateName)} />
<PlaybackController controller={controller} theme="dark" />
</DiagramProvider>
);
}Props — PlaybackControllerProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| controller | PlaybackControllerState | (required) | State object from usePlaybackController(). |
| theme | "light" \| "dark" | "light" | Theme for the controller UI. |
| className | string | — | Additional CSS class. |
| keyboardEnabled | boolean | true | Enable keyboard shortcuts (Space = play/pause, ←/→ = step). |
ZoomControls
Floating zoom control buttons for interactive viewers. Includes zoom in, zoom out, fit-to-view, and reset (1:1) buttons with a scale percentage display.
<ZoomControls
onZoomIn={zoomIn}
onZoomOut={zoomOut}
onFitToView={fitToView}
onResetZoom={resetZoom}
currentScale={1.0}
position="bottom-right"
theme="light"
/>Props — ZoomControlsProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| onZoomIn | () => void | (required) | Zoom in callback. |
| onZoomOut | () => void | (required) | Zoom out callback. |
| onFitToView | () => void | (required) | Fit-to-view callback. |
| onResetZoom | () => void | (required) | Reset to 1:1 callback. |
| currentScale | number | (required) | Current scale (1.0 = 100%). |
| theme | "light" \| "dark" | "light" | Theme. |
| position | "top-left" \| "top-right" \| "bottom-left" \| "bottom-right" | "bottom-right" | Corner position. |
| className | string | — | Additional CSS class. |
CopyImageButton
Floating button to copy the rendered diagram as a PNG image to the clipboard. Falls back to file download when the Clipboard API is unavailable.
const containerRef = useRef<HTMLDivElement>(null);
<div ref={containerRef}>
<DiagramViewer ast={ast} />
<CopyImageButton containerRef={containerRef} position="top-right" />
</div>Props — CopyImageButtonProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| containerRef | RefObject<HTMLElement> | (required) | Ref to the SVG container. |
| theme | "light" \| "dark" | "light" | Theme. |
| position | "top-left" \| "top-right" \| "bottom-left" \| "bottom-right" | "top-right" | Corner position. |
| pixelRatio | number | 2 | Pixel ratio for rasterization (2 = Retina). |
| backgroundColor | string | "transparent" | Background color of the rasterized image. |
| padding | number | 16 | Padding around the diagram in the image (px). |
| downloadFilename | string | "diagram" | Filename for fallback download (without extension). |
| onSuccess | () => void | — | Called after successful copy. |
| onFallbackDownload | () => void | — | Called on fallback to file download. |
| onError | (message: string) => void | — | Called on error. |
| className | string | — | Additional CSS class. |
DiagramTooltip
Interactive tooltip wrapper for diagram elements. Shows rich, cursor-tracking tooltips on hover/touch for arcs (pie/donut), circles (venn), and nodes (graph).
<DiagramTooltip theme="dark" enabled selector=".imd-arc, .imd-circle">
<div dangerouslySetInnerHTML={{ __html: svgString }} />
</DiagramTooltip>Props — DiagramTooltipProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| children | ReactNode | (required) | Content to wrap. |
| theme | "light" \| "dark" | "light" | Tooltip theme. |
| enabled | boolean | true | Whether tooltips are enabled. |
| selector | string | ".imd-arc, .imd-circle, .imd-node-group" | CSS selector for tooltip-triggering elements. |
The tooltip reads data-label, data-value, data-percentage, data-node-id, data-set-id, and data-id attributes from the matched SVG elements.
ThemeToggle
Light/dark theme toggle button with animated sun/moon icons.
import { ThemeToggle, useThemePreference } from "@immediate-diagram/react";
function Header() {
const { theme, toggleTheme } = useThemePreference();
return <ThemeToggle theme={theme} onToggle={toggleTheme} />;
}Props — ThemeToggleProps
| Prop | Type | Description |
|------|------|-------------|
| theme | "light" \| "dark" | Currently active theme. |
| onToggle | () => void | Toggle callback. |
Note: Requires
@immediate-diagram/react/styles.cssfor full visual styling.
Toast & ToastContainer
Notification system with auto-dismiss. Use the useToast hook for state management.
import { ToastContainer, useToast } from "@immediate-diagram/react";
import "@immediate-diagram/react/styles.css";
function App() {
const { toasts, addToast } = useToast();
return (
<>
<button onClick={() => addToast("Diagram copied!", "success")}>
Copy
</button>
<ToastContainer toasts={toasts} onDismiss={(id) => {/* handled by hook */}} />
</>
);
}ToastMessage
| Field | Type | Description |
|-------|------|-------------|
| id | string | Unique toast ID. |
| message | string | Message text. |
| variant | "success" \| "error" \| "info" | Visual variant (green/red/blue). |
ToastContainerProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| toasts | ToastMessage[] | (required) | Active toasts. |
| onDismiss | (id: string) => void | (required) | Dismiss callback. |
| duration | number | 3000 | Auto-dismiss duration (ms). |
ConfirmDialog
Accessible confirmation modal dialog.
<ConfirmDialog
open={isOpen}
title="Delete Diagram"
message="Are you sure? This action cannot be undone."
confirmLabel="Delete"
confirmVariant="danger"
onConfirm={handleDelete}
onCancel={() => setIsOpen(false)}
/>Props — ConfirmDialogProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| open | boolean | (required) | Whether the dialog is visible. |
| title | string | (required) | Dialog title. |
| message | string | (required) | Dialog message. |
| confirmLabel | string | "Confirm" | Confirm button label. |
| confirmVariant | "danger" \| "primary" | "primary" | Confirm button variant. |
| onConfirm | () => void | (required) | Confirm callback. |
| onCancel | () => void | (required) | Cancel callback. |
DiagramWrapper
Theme-aware container that provides background styling, border, and visual boundary detection. Used internally by DiagramViewer but can be used standalone for custom compositions.
When the page theme and diagram theme differ, a subtle visual boundary (border) is automatically added to make the contrast feel intentional.
<DiagramWrapper metadata={ast.metadata} pageTheme="dark" padding={24}>
{/* your diagram content */}
</DiagramWrapper>Props — DiagramWrapperProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| metadata | DiagramMetadata \| null | — | AST metadata for theme resolution. |
| children | ReactNode | (required) | Content. |
| className | string | — | Additional CSS class. |
| style | CSSProperties | — | Additional styles. |
| pageTheme | "light" \| "dark" | auto | Page-level theme (auto-detected from DOM). |
| borderRadius | number | 8 | Border radius (px). |
| padding | number | 16 | Padding (px). |
| aria-label | string | — | Accessible label. |
| data-testid | string | — | Test ID. |
DiagramErrorBoundary
React error boundary for diagram rendering. Catches errors from layout, rendering, or SVG injection and displays a fallback UI with a retry button.
<DiagramErrorBoundary
onError={(error, info) => logToSentry(error, info)}
fallback={(error, reset) => (
<div>
<p>Something went wrong: {error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)}
>
<DiagramViewer ast={ast} />
</DiagramErrorBoundary>Props — DiagramErrorBoundaryProps
| Prop | Type | Description |
|------|------|-------------|
| children | ReactNode | Components to wrap. |
| onError | (error, errorInfo) => void | Error callback (for logging/telemetry). |
| fallback | (error, reset) => ReactNode | Custom fallback UI. |
RecoveryErrorPanel
Displays multiple parse errors with line numbers, source context, and "Did you mean?" suggestions. Used internally by DiagramViewerFromSource and ImmediateDiagram.
<RecoveryErrorPanel
errors={parseResult.errors}
source={dslSource}
hasPartialDiagram={parseResult.ast !== null}
/>Props — RecoveryErrorPanelProps
| Prop | Type | Description |
|------|------|-------------|
| errors | RecoveryParseError[] | Parse errors to display. |
| source | string | Original DSL source for context. |
| hasPartialDiagram | boolean | Whether partial AST was recovered. |
| data-testid | string | Test ID (default: "recovery-error-panel"). |
LoadingSpinner
Branded loading indicator with animated spinner ring and customizable message.
<LoadingSpinner message="Rendering diagram…" />Props — LoadingSpinnerProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| message | string | "Loading…" | Message below the spinner. |
| data-testid | string | "loading-spinner" | Test ID. |
Note: Requires
@immediate-diagram/react/styles.cssfor the animation.
Button
Generic styled button with variant support.
<Button variant="primary" onClick={handleSave}>Save</Button>
<Button variant="danger" onClick={handleDelete} disabled>Delete</Button>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| children | ReactNode | (required) | Button content. |
| onClick | () => void | — | Click handler. |
| disabled | boolean | false | Disabled state. |
| variant | "primary" \| "secondary" \| "danger" | "primary" | Visual variant. |
Context & Provider
DiagramProvider
Provides the layout engine, text measurement (Canvas-based with font fallback), and theme configuration to all diagram components in the tree.
Required for: DiagramViewer, DiagramViewerFromSource, InteractiveDiagramViewer.
Not required for: ImmediateDiagram (creates its own provider internally).
import { DiagramProvider, DiagramViewer } from "@immediate-diagram/react";
function App() {
return (
<DiagramProvider theme="dark" fontFamily="Fira Code, monospace">
<DiagramViewer ast={ast1} />
<DiagramViewer ast={ast2} />
</DiagramProvider>
);
}Props — DiagramProviderProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| fontFamily | string | "Inter, system-ui, Avenir, Helvetica, Arial, sans-serif" | CSS font-family for text measurement. |
| layoutEngine | LayoutEngine | auto | Pre-configured layout engine (auto-created if omitted). |
| theme | "light" \| "dark" \| Partial<DiagramTheme> | "light" | Theme preset or partial theme override. |
| children | ReactNode | (required) | Child components. |
Theme Priority
Theme resolution follows this priority:
DiagramProviderthemeprop — highest priority (programmatic override)- DSL
theme darkdirective — from the diagram source - Default light theme — lowest priority
When DiagramProvider has an explicit theme prop, it overrides all DSL theme directives. When omitted, individual diagrams can control their own theme via the DSL.
useDiagramContext
Access the full diagram context value:
const { measureText, fontsReady, layoutEngine, theme, hasExplicitTheme } = useDiagramContext();Returns a DiagramContextValue with:
| Field | Type | Description |
|-------|------|-------------|
| measureText | MeasureTextFn | Text measurement function (Canvas-backed or heuristic). |
| fontsReady | boolean | Whether web fonts have loaded (measurement is accurate). |
| layoutEngine | LayoutEngine | Shared layout engine with all strategies. |
| theme | DiagramTheme | Current theme configuration. |
| hasExplicitTheme | boolean | Whether the provider has a programmatic theme set. |
Convenience Hooks
These hooks are shortcuts for accessing specific context values:
| Hook | Returns | Description |
|------|---------|-------------|
| useEffectiveTheme(metadata) | DiagramTheme | Resolves theme considering provider + DSL theme directive. |
| useDiagramTheme() | DiagramTheme | Returns the provider's theme directly. |
| useFontsReady() | boolean | Whether web fonts are loaded. |
| useLayoutEngine() | LayoutEngine | The shared layout engine. |
| useMeasureText() | MeasureTextFn | The text measurement function. |
DiagramSkeleton
Loading placeholder shown while fonts are initializing:
import { DiagramSkeleton } from "@immediate-diagram/react";
<DiagramSkeleton width={600} height={400} variant="shimmer" />| Prop | Type | Default | Description |
|------|------|---------|-------------|
| width | number | 600 | Width (px). |
| height | number | 400 | Height (px). |
| variant | "shimmer" \| "pulse" \| "none" | auto | Animation variant (auto-detects prefers-reduced-motion). |
| className | string | — | Additional CSS class. |
Hooks
Core Hooks
usePlaybackController(options)
Headless state machine for timeline playback. Powers the PlaybackController component.
const controller = usePlaybackController({
timelines: ast.timelines,
transitions: ast.transitions,
});
// controller.play(), controller.pause(), controller.stepForward(), ...
// controller.currentStateName, controller.stepIndex, controller.totalSteps, ...Options — UsePlaybackControllerOptions:
| Option | Type | Description |
|--------|------|-------------|
| timelines | TimelineBlock[] | Timeline blocks from the parsed AST. |
| transitions | TransitionBlock[] | Transition blocks (for default inference). |
| managerOptions | object | Custom TimelineManager options. |
Returns — PlaybackControllerState:
| Field | Type | Description |
|-------|------|-------------|
| playbackState | PlaybackState | Current state (idle, playing, paused). |
| stepIndex | number | Current step (0-based). |
| totalSteps | number | Total steps in the active timeline. |
| stateLabels | string[] | Ordered state names. |
| currentStateName | string | Current state name. |
| loop | boolean | Whether looping is enabled. |
| speed | SpeedOption | Current speed (0.5, 1, or 2). |
| activeTimelineName | string \| null | Active timeline name. |
| timelineNames | string[] | All available timelines. |
| hasTimelines | boolean | Whether any timelines exist. |
| play | () => void | Start/resume playback. |
| pause | () => void | Pause playback. |
| togglePlayPause | () => void | Toggle play/pause. |
| stepForward | () => void | Go to next state. |
| stepBack | () => void | Go to previous state. |
| goToStep | (index: number) => void | Jump to a specific step. |
| toggleLoop | () => void | Toggle looping. |
| setSpeed | (speed: SpeedOption) => void | Set speed multiplier. |
| setTimeline | (name: string) => void | Switch timeline. |
useRecoveryParse(source, maxErrors?)
Parses DSL source with error recovery — collects ALL parse errors (not just the first) and produces a partial AST when possible.
const { ast, errors, isComplete, skippedLines } = useRecoveryParse(dslSource);
if (ast) {
// Render the (possibly partial) diagram
}
if (errors.length > 0) {
// Show errors
}Returns — RecoveryParseState:
| Field | Type | Description |
|-------|------|-------------|
| ast | DiagramAST \| null | Narrowed AST (null if header failed). |
| errors | RecoveryParseError[] | All collected parse errors. |
| isComplete | boolean | Whether parsing succeeded without errors. |
| skippedLines | number[] | Line numbers skipped during recovery. |
useTimelinePlayback(diagram)
Lower-level timeline hook wrapping the headless TimelineManager for React.
const {
timelineNames, activeTimeline, hasTimelines,
playbackState, currentState,
play, pause, stepForward, stepBack, goToState,
} = useTimelinePlayback(diagram);useBlockCollapseState(allBlockIds)
Manages collapse/expand state for block diagram nodes.
import { useBlockCollapseState, collectAllBlockIds } from "@immediate-diagram/react";
const blockIds = collectAllBlockIds(blockAst);
const { collapsedBlocks, toggleBlock, expandAll, collapseAll, isCollapsed } =
useBlockCollapseState(blockIds);Returns — UseBlockCollapseStateReturn:
| Field | Type | Description |
|-------|------|-------------|
| collapsedBlocks | Set<string> | Currently collapsed block IDs. |
| toggleBlock | (id: string) => void | Toggle a block. |
| expandAll | () => void | Expand all blocks. |
| collapseAll | () => void | Collapse all blocks. |
| isCollapsed | (id: string) => boolean | Check if a block is collapsed. |
Viewport Hooks
useViewportTransform(options)
Manages zoom/pan state for interactive diagram viewers. Uses direct DOM manipulation for 60fps performance during continuous interactions.
const {
containerRef, viewportState,
zoomIn, zoomOut, zoomTo, fitToView, resetZoom,
panBy, resetPan,
} = useViewportTransform({
svgWidth: 800,
svgHeight: 600,
minScale: 0.1,
maxScale: 10,
});
return <div ref={containerRef}>{/* SVG content */}</div>;Key options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| svgWidth | number | (required) | SVG content width (from viewBox). |
| svgHeight | number | (required) | SVG content height (from viewBox). |
| minScale | number | 0.1 | Minimum zoom scale. |
| maxScale | number | 10 | Maximum zoom scale. |
| zoomStep | number | 0.1 | Scale delta per wheel tick. |
| enabled | boolean | true | Enable/disable interactions. |
Returns — UseViewportTransformReturn:
| Field | Type | Description |
|-------|------|-------------|
| containerRef | RefObject<HTMLDivElement> | Attach to the SVG container. |
| viewportState | ViewportState | Current { scale, translateX, translateY }. |
| zoomIn / zoomOut | () => void | Zoom step functions. |
| zoomTo | (scale: number) => void | Zoom to specific scale. |
| fitToView | () => void | Fit diagram to container. |
| resetZoom | () => void | Reset to 1:1 centered. |
| panBy | (dx, dy) => void | Pan by pixel delta. |
| resetPan | () => void | Reset pan to origin. |
| applyTransformToDOM | () => void | Apply current state to DOM (for gesture hooks). |
| syncToReactState | () => void | Sync DOM state to React (call on gesture end). |
usePinchZoom(options)
Multi-touch gesture support for mobile/tablet. Handles single-finger drag (pan) and two-finger pinch (zoom).
usePinchZoom({
containerRef,
viewportStateRef: transform.viewportStateRef,
applyTransform: transform.applyTransformToDOM,
minScale: 0.1,
maxScale: 10,
onGestureEnd: transform.syncToReactState,
});This hook is used internally by InteractiveDiagramViewer and typically doesn't need to be used directly.
useViewportKeyboard(options)
Keyboard shortcuts for diagram viewport navigation:
| Key | Action |
|-----|--------|
| + / = | Zoom in |
| - | Zoom out |
| 0 | Reset zoom to 100% |
| Arrow keys | Pan (50px step) |
| Shift + Arrow | Pan fast (200px step) |
| Home | Fit to view |
| End | Reset pan |
Used internally by InteractiveDiagramViewer.
Utility Hooks
useDebouncedValue(value, delayMs?)
Debounces a value change. Returns the last stable value after delayMs of inactivity.
const debouncedSource = useDebouncedValue(liveSource, 200);
// debouncedSource updates 200ms after the last liveSource changeuseThemePreference()
Manages light/dark theme preference with localStorage persistence and system preference detection.
const { theme, toggleTheme, setTheme, isUserSet } = useThemePreference();Returns — ThemePreference:
| Field | Type | Description |
|-------|------|-------------|
| theme | "light" \| "dark" | Active theme. |
| toggleTheme | () => void | Toggle and persist. |
| setTheme | (theme) => void | Set explicitly and persist. |
| isUserSet | boolean | Whether the user explicitly chose (vs. system default). |
usePageTheme()
Reactively tracks the page-level data-theme attribute on <html> via a shared singleton MutationObserver.
const pageTheme = usePageTheme(); // "light" | "dark"useReducedMotion()
Detects prefers-reduced-motion: reduce media query. Re-renders on preference change.
const prefersReducedMotion = useReducedMotion();
// true when the user has enabled reduced motion in OS settingsuseLazyRender(ref, options?)
IntersectionObserver-based lazy rendering. The target element renders a placeholder until it scrolls into view.
const containerRef = useRef<HTMLDivElement>(null);
const { hasBeenVisible } = useLazyRender(containerRef, { rootMargin: "100px" });
return (
<div ref={containerRef}>
{hasBeenVisible ? <DiagramViewer ast={ast} /> : <DiagramSkeleton />}
</div>
);Returns — UseLazyRenderReturn:
| Field | Type | Description |
|-------|------|-------------|
| hasBeenVisible | boolean | Latching — once true, stays true. |
| isIntersecting | boolean | Currently in viewport (dynamic). |
useCopyDiagramAsImage(options)
Copy a rendered SVG diagram to clipboard as PNG.
const { copyAsImage, status, errorMessage } = useCopyDiagramAsImage({
containerRef: diagramRef,
pixelRatio: 2,
backgroundColor: "#ffffff",
});Status values: "idle" | "copying" | "success" | "fallback-download" | "error"
useSplitPane(options?)
Resizable split-pane layout for editor/preview layouts.
const { ratio, isDragging, handlePointerDown, setRatio } = useSplitPane({
initialRatio: 0.5,
minRatio: 0.2,
maxRatio: 0.8,
});
return (
<div style={{ display: "flex" }}>
<div style={{ width: `${ratio * 100}%` }}>Editor</div>
<div onPointerDown={handlePointerDown} style={{ cursor: "col-resize", width: 8 }} />
<div style={{ width: `${(1 - ratio) * 100}%` }}>Preview</div>
</div>
);Utilities
Exported utility functions for advanced use cases:
Block Collapse
import {
collapseBlocks, // Transform AST to collapse specified blocks
collectAllBlockIds, // Get all block IDs from a block diagram AST
collectBlockChildCounts, // Count children per block
getBlockId, // Get the canonical ID for a block
slugifyBlockLabel, // Slugify a block label for use as an ID
} from "@immediate-diagram/react";Syntax Highlighting
import { tokenizeLine, tokensToHtml } from "@immediate-diagram/react";
const tokens = tokenizeLine("A --> B");
const html = tokensToHtml(tokens);
// <span class="imd-syn-identifier">A</span> <span class="imd-syn-arrow">--></span> ...SVG Processing
import {
extractSvgDimensions, // Extract width/height from SVG viewBox
injectViewportGroup, // Inject a <g> wrapper for zoom/pan transforms
sanitizeSvg, // Sanitize SVG through DOMPurify
DOMPURIFY_SVG_CONFIG, // DOMPurify config optimized for SVG diagrams
} from "@immediate-diagram/react";Built-in Examples
import { EXAMPLE_DIAGRAMS, EXAMPLE_TYPES } from "@immediate-diagram/react";
// 15 pre-built example diagrams covering all 7 diagram types
EXAMPLE_DIAGRAMS.forEach(({ id, type, title, description, source, tags }) => {
console.log(`${type}: ${title}`);
});Theming
Provider-Level Theme
Set the theme for all diagrams in the tree:
// Preset
<DiagramProvider theme="dark">
// Partial custom overrides (merged with defaults)
<DiagramProvider theme={{ name: "custom", background: "#1a1a2e", nodeStroke: "#e94560" }}>Per-Diagram Theme via DSL
Individual diagrams can declare their theme in the DSL:
flowchart
theme dark
A --> B --> CWhen DiagramProvider has no explicit theme prop, the DSL theme directive takes effect.
System Theme Detection
import { useThemePreference, usePageTheme } from "@immediate-diagram/react";
// Full preference management (with localStorage persistence)
const { theme, toggleTheme } = useThemePreference();
// Read-only page theme tracking (watches <html data-theme="...">)
const pageTheme = usePageTheme();Examples
Basic Rendering
import { ImmediateDiagram } from "@immediate-diagram/react";
function BasicExample() {
return (
<ImmediateDiagram
dsl={`flowchart
Start("Start") --> Check{"Ready?"}
Check -->|Yes| Deploy["Deploy 🚀"]
Check -->|No| Wait("Wait") --> Check`}
/>
);
}Dark Theme
<ImmediateDiagram
dsl={`sequence
Client ->> Server: POST /api/login
Server ->> DB: SELECT user
DB -->> Server: User record
Server -->> Client: 200 JWT token`}
theme="dark"
/>Interactive Viewer with Zoom/Pan
<ImmediateDiagram
dsl={`architecture
group "Frontend" {
React["React App"]
Redux["State"]
}
group "Backend" {
API["REST API"]
DB[("Database")]
}
React --> API
Redux --> React
API --> DB`}
interactive
showZoomControls
initialZoom="fit"
/>Timeline Playback
<ImmediateDiagram
dsl={`pie "Market Share"
"Chrome": 65
"Safari": 19
"Firefox": 4
"Edge": 4
"Other": 8
@state "2023" {
"Chrome": 63
"Safari": 20
"Firefox": 5
}
@state "2024" {
"Chrome": 65
"Safari": 19
"Firefox": 4
}
@timeline "growth" {
default --> "2023" --> "2024"
}`}
showControls
autoPlay
/>Custom Viewer with Hooks
Build a completely custom diagram viewer using the lower-level hooks:
import { parse, narrowDiagram } from "@immediate-diagram/parser";
import { renderDiagram } from "@immediate-diagram/renderer";
import {
DiagramProvider,
useDiagramContext,
useEffectiveTheme,
} from "@immediate-diagram/react";
function CustomViewer({ source }: { source: string }) {
const { measureText, layoutEngine, fontsReady } = useDiagramContext();
const doc = parse(source);
const ast = narrowDiagram(doc);
const theme = useEffectiveTheme(ast.metadata);
if (!fontsReady) return <p>Loading fonts…</p>;
const positioned = layoutEngine.layout(ast, { measureText });
const svg = renderDiagram(positioned, { theme });
return <div dangerouslySetInnerHTML={{ __html: svg }} />;
}
function App() {
return (
<DiagramProvider theme="dark">
<CustomViewer source="flowchart\n A --> B" />
</DiagramProvider>
);
}Multiple Diagrams on One Page
Share a single provider for efficient resource reuse:
import { DiagramProvider, DiagramViewerFromSource } from "@immediate-diagram/react";
const diagrams = [
"flowchart\n A --> B --> C",
"pie\n \"React\": 60\n \"Vue\": 25\n \"Angular\": 15",
"venn\n {A} \"Frontend\"\n {B} \"Backend\"\n {A,B} \"Fullstack\"",
];
function Dashboard() {
return (
<DiagramProvider>
{diagrams.map((dsl, i) => (
<DiagramViewerFromSource key={i} source={dsl} lazy />
))}
</DiagramProvider>
);
}Use lazy to defer layout computation for off-screen diagrams.
Error Handling
import { ImmediateDiagram } from "@immediate-diagram/react";
<ImmediateDiagram
dsl={userInput}
onError={(error, info) => {
console.error("Render error:", error);
reportToSentry(error, info);
}}
errorFallback={(error, reset) => (
<div style={{ padding: 16, color: "red" }}>
<p>Failed to render: {error.message}</p>
<button onClick={reset}>Retry</button>
</div>
)}
renderError={(error, source) => (
<pre style={{ color: "orange" }}>
Parse error at line {error.location?.start?.line}: {error.message}
</pre>
)}
/>Lazy Loading
For pages with many diagrams, enable lazy rendering to avoid computing layout for off-screen content:
<DiagramProvider>
{allDiagrams.map((ast, i) => (
<DiagramViewer
key={i}
ast={ast}
lazy
lazyRootMargin="200px" // Start rendering 200px before viewport entry
/>
))}
</DiagramProvider>Copy to Clipboard
import { useRef } from "react";
import { DiagramProvider, DiagramViewer, CopyImageButton } from "@immediate-diagram/react";
function CopyableViewer({ ast }) {
const containerRef = useRef<HTMLDivElement>(null);
return (
<DiagramProvider>
<div ref={containerRef} style={{ position: "relative" }}>
<DiagramViewer ast={ast} />
<CopyImageButton
containerRef={containerRef}
position="top-right"
pixelRatio={2}
backgroundColor="#ffffff"
/>
</div>
</DiagramProvider>
);
}CSS (Optional)
Import the optional stylesheet for enhanced visual styling:
import "@immediate-diagram/react/styles.css";All components work without this import (graceful degradation). The CSS provides:
.imd-syn-*— Syntax highlighting colors for IMD DSL (light + dark themes).theme-toggle— Theme toggle button styling with hover effects.toast-*— Toast notification animations and variant colors.loading-spinner-*— Animated loading indicator@media (prefers-reduced-motion)— Accessibility: disables animations when user prefers reduced motion
Migration from @immediate-diagram/web
If you're currently importing diagram components from @immediate-diagram/web, migration is straightforward:
1. Install the react package
npm install @immediate-diagram/react2. Update imports
- import { ImmediateDiagram } from "@immediate-diagram/web";
+ import { ImmediateDiagram } from "@immediate-diagram/react";
- import { DiagramViewer, DiagramProvider } from "@immediate-diagram/web";
+ import { DiagramViewer, DiagramProvider } from "@immediate-diagram/react";
- import "@immediate-diagram/web/styles.css";
+ import "@immediate-diagram/react/styles.css";3. API compatibility
All component props, hook signatures, and context APIs are identical. The react package is a direct extraction of the diagram components from the web package — no breaking changes.
4. What stays in @immediate-diagram/web
The web package retains page-level concerns: routing, authentication, dashboard, editor UI. The react package contains all reusable diagram rendering components, hooks, and utilities.
TypeScript Support
This package is written in TypeScript and ships .ts source files. All components, hooks, props interfaces, and utility types are fully typed and exported.
Key exported types:
import type {
// Component props
ImmediateDiagramProps,
ImmediateDiagramRef,
DiagramViewerProps,
DiagramViewerFromSourceProps,
InteractiveDiagramViewerProps,
PlaybackControllerProps,
ZoomControlsProps,
CopyImageButtonProps,
DiagramTooltipProps,
ThemeToggleProps,
DiagramWrapperProps,
DiagramErrorBoundaryProps,
RecoveryErrorPanelProps,
LoadingSpinnerProps,
ConfirmDialogProps,
ToastMessage,
ToastContainerProps,
TooltipData,
// Context
DiagramProviderProps,
DiagramContextValue,
DiagramSkeletonProps,
SkeletonVariant,
// Hook return types
PlaybackControllerState,
SpeedOption,
RecoveryParseState,
UseBlockCollapseStateReturn,
UseViewportTransformReturn,
ViewportState,
UseLazyRenderReturn,
UseSplitPaneReturn,
ThemePreference,
UseTimelinePlaybackReturn,
CopyImageStatus,
UseCopyDiagramAsImageReturn,
// Utility types
SyntaxToken,
ExampleDiagram,
ExampleTypeFilter,
ZoomControlsPosition,
CopyImageButtonPosition,
Point,
} from "@immediate-diagram/react";Browser Support
- Chrome / Edge: 90+
- Firefox: 90+
- Safari: 15+
- Mobile Safari: 15+
- Chrome Android: 90+
Requires:
IntersectionObserver(forlazyrendering — falls back to eager rendering)ResizeObserver(for fit-to-view calculations — falls back gracefully)- Canvas API (for text measurement — falls back to heuristic estimation)
- Clipboard API (for copy-to-clipboard — falls back to file download)
Troubleshooting
"Cannot find module '@immediate-diagram/react'"
Ensure you've installed the package and its peer dependencies:
npm install @immediate-diagram/react react react-domDiagram renders with wrong text sizing
Text measurement depends on the Canvas API and font loading. If custom fonts haven't loaded yet, the component shows a skeleton placeholder. If text sizing is consistently wrong, check that the fontFamily prop on DiagramProvider matches the fonts used in your app.
Blank diagram (no SVG output)
- Check the browser console for parse errors.
- Verify the DSL syntax — use
DiagramViewerFromSourcewhich shows parse errors automatically. - Ensure
DiagramProviderwraps your viewer components (not needed forImmediateDiagram).
Interactive zoom/pan not working
- Ensure
interactive={true}is set onImmediateDiagramor useInteractiveDiagramViewer. - For keyboard shortcuts, the diagram container must have focus (
tabIndex={0}is set automatically). - On mobile, touch events require the diagram container to be large enough to interact with.
DOMPurify errors in SSR / Node.js
DOMPurify requires a DOM environment. In SSR contexts, either:
- Set
sanitize={false}(the default) — the rendering pipeline already escapes user text. - Use
isomorphic-dompurifyin your SSR setup.
Timeline playback controls not showing
The playback controller only appears when the diagram has @state or @timeline blocks. With 0–1 states, no controls are rendered. With 2 states, a compact toggle appears. With 3+ states, the full controller is shown.
Theme mismatch between page and diagram
Use DiagramProvider theme="dark" to match your page theme, or let diagrams control their own theme via the DSL theme dark directive. The DiagramWrapper automatically adds a visual boundary when themes differ.
Performance with many diagrams
For pages with 20+ diagrams, enable lazy rendering:
<DiagramViewer ast={ast} lazy lazyRootMargin="200px" />This defers layout computation until each diagram scrolls into view.
API Reference
All public APIs are documented with JSDoc in the source files. The exports are organized into four categories:
| Category | Exports |
|----------|---------|
| Components | ImmediateDiagram, DiagramViewer, DiagramViewerFromSource, InteractiveDiagramViewer, PlaybackController, ZoomControls, CopyImageButton, DiagramTooltip, ThemeToggle, ToastContainer, ConfirmDialog, DiagramWrapper, DiagramErrorBoundary, RecoveryErrorPanel, LoadingSpinner, Button |
| Context | DiagramProvider, DiagramSkeleton, useDiagramContext, useEffectiveTheme, useDiagramTheme, useFontsReady, useLayoutEngine, useMeasureText, checkFontAvailable, extractPrimaryFont |
| Hooks | usePlaybackController, useRecoveryParse, useTimelinePlayback, useBlockCollapseState, useViewportTransform, usePinchZoom, useViewportKeyboard, useDebouncedValue, useThemePreference, usePageTheme, useReducedMotion, useLazyRender, useCopyDiagramAsImage, useSplitPane, useToast |
| Utilities | collapseBlocks, collectAllBlockIds, collectBlockChildCounts, getBlockId, slugifyBlockLabel, tokenizeLine, tokensToHtml, extractSvgDimensions, injectViewportGroup, sanitizeSvg, DOMPURIFY_SVG_CONFIG, clampScale, computeCursorZoom, computeFitToView, touchDistance, touchMidpoint, computePinchScale, isClipboardWriteSupported, downloadBlob, rasterizeSvgToBlob, postProcessCollapseIndicators, EXAMPLE_DIAGRAMS, EXAMPLE_TYPES, SPEED_OPTIONS, PAN_STEP, PAN_FAST_STEP, COLLAPSED_BLOCK_WIDTH, COLLAPSED_BLOCK_HEIGHT |
For detailed type information, see the TypeScript source at src/index.ts and the individual module files.
License
MIT
