Components and JavaScript API
This page covers the JavaScript/TypeScript surface of the React binding:
the <DataFlowPlayer> component and its props, icon registration, and syntax
highlighting. For the shape of the JSON specification (spec), see the
API Reference (JSON spec).
The same options exist in the three other packages —
<dataflow-player> (custom element), <dfa-player> (Angular) and
mountPlayer (the core, no framework). The prop table below is the reference
for all of them; what differs is only how you pass them. See
Packages and bindings.
import { DataFlowPlayer } from '@dataflow-animator/react';
import '@dataflow-animator/core/styles.css';
<DataFlowPlayer>
The component that compiles a spec and displays it in a media player.
Only the spec prop is required.
| Prop | Type | Default | Role |
|---|---|---|---|
spec | DataFlowSpec | — (required) | The specification to animate. |
height | number | string | 420 | Scene height (e.g. 420, '60vh'). |
controls | boolean | true | Shows the playback bar and step navigation. |
exportable | boolean | false | Adds a button that opens the JSON spec (copy / download). |
videoExport | boolean | VideoExportConfig | false | Adds a button that renders the animation to a WebM, MP4 or GIF file. |
autoPlay | boolean | false | Starts playback automatically. |
loop | boolean | false | Loops playback at the end. |
speed | number | 1 | Playback speed (1 = normal). |
theme | PlayerTheme | 'default' | Visual palette (see below). |
mode | 'light' | 'dark' | 'auto' | 'auto' | Which variant of theme to show. |
density | 'compact' | 'comfortable' | 'spacious' | 'comfortable' | Visual density (element size / spacing). |
width | number | string | container | Stage width. Read at mount, before the first measurement. |
initialT | number | 0 | Instant the player opens at, in ms. Read once, at mount. |
debug | boolean | false | Internal timeline inspection overlay. |
highlight | Highlighter | Prism | Custom syntax highlighting (see below). |
fallback | ReactNode | — | Rendered on the server and until the player mounts, in place of the loading indicator. |
className | string | — | Additional CSS class on the root container. |
style | CSSProperties | — | Inline styles on the root container. |
labels | Partial<PlayerLabels> | English | Localises the player chrome (see below). |
The player reads its options once, when it mounts, so changing any of them —
spec included — rebuilds it. The current instant and play state are carried
across, so scrubbing or editing a spec live stays smooth.
Remounting is keyed on the spec's structure, not its referential identity: a literal object rebuilt on every parent render costs a serialisation, not a rebuild. Memoizing it is still worthwhile on a hot render path, but it is no longer the difference between smooth and stuttering.
Only the first mount honours initialT and autoPlay: every remount after
that reopens at the current instant and play state instead, which is exactly
what makes editing options mid-scrub invisible.
The first mount is also the only one that waits for a paint (two frames), so the placeholder — and its loading indicator — is really on screen before the spec is compiled and measured. A remount happens immediately: the previous player is still there, and trading it for two frames of empty box would be a blink.
// fine — an equal spec does not remount anything
<DataFlowPlayer spec={{ nodes: [...], timeline: [...] }} />
// cheaper still on a frequently re-rendering parent
const spec = useMemo(() => ({ nodes: [...], timeline: [...] }), []);
<DataFlowPlayer spec={spec} />
For SSR rendering / hydration placeholder and Next.js / Docusaurus integration, see Installation.
Themes
The look of the player is two independent choices:
theme— the palette (default,dots,blueprint,pcb,chalk,terminal,paper,neon);mode— which variant of that palette to show (light,dark,auto).
Every theme ships both a light and a dark variant, so switching one axis never
forces the other. mode="auto" (the default) follows the host site when an
ancestor carries data-theme="light|dark" (the Docusaurus convention), and the
OS preference otherwise — the resolution is pure CSS, so it stays SSR-safe and
needs no JavaScript.
// A chalkboard that still follows the reader's light/dark preference.
<DataFlowPlayer spec={spec} theme="chalk" mode="auto" />
// A PCB pinned to its dark variant, whatever the host does.
<DataFlowPlayer spec={spec} theme="pcb" mode="dark" />
If you style the player yourself, note that the root element carries
data-theme (the palette) and data-mode (the light/dark variant), so
your own CSS can hook either axis independently.
- default
- dots
- blueprint
- pcb
- chalk
- terminal
- paper
- neon
Toggle this site's own light/dark switch to see the other variant of whichever
theme is selected — the players above are all on mode="auto".
pcb and electrical schematicsWith direction: 'circuit', wires are tinted by their net (the driving
signal), which deliberately takes precedence over the palette's arrow colour:
that colour carries meaning. The pcb theme still themes the board, the
components and the labels around them.
Each palette is a set of --rdfa-* CSS variables scoped under .rdfa-player.
To tweak one, override the variables on your own container rather than forking
the stylesheet:
.my-diagram .rdfa-player {
--rdfa-accent: light-dark(#b91c1c, #f87171);
}
Exporting the spec (exportable)
With exportable, a button appears in the controls bar; it opens the
JSON specification (colored) in a window, with two actions:
- Copy to clipboard;
- Download as a
.jsonfile.
<DataFlowPlayer spec={spec} exportable />
Copying and downloading are browser-side operations
(navigator.clipboard, download anchor): they have no effect during SSR, the
window only opens after hydration. Has no effect if controls is false.
Exporting a video (videoExport)
With videoExport, a second button appears in the controls bar. It opens a
small panel — format, resolution, frame rate — and writes the animation to a
file.
<DataFlowPlayer spec={spec} videoExport />
Pass an object to narrow what the panel offers:
<DataFlowPlayer
spec={spec}
videoExport={{ formats: ['mp4'], resolutions: [1080], frameRates: [30] }}
/>
| Field | Type | Default | Meaning |
|---|---|---|---|
formats | VideoExportFormat[] | all three | Which formats the panel offers, in order. |
resolutions | number[] | [360, 540, 720, 1080] | Output heights offered, in pixels — 720 reads 720p. |
frameRates | number[] | [15, 20, 24, 30, 60] | Frame rates offered. |
bitrate | number | 4_000_000 | Video bitrate. Ignored by GIF. |
filename | string | 'dataflow' | Base name of the file, extension excluded. |
A list with a single entry stops being a question: the control is shown as
plain text instead of a dropdown, so the value is still visible without being
adjustable. { formats: ['mp4'], resolutions: [1080] } therefore pins the
output and leaves only the frame rate to choose.
The panel estimates before it commits
The three settings interact, so the panel shows how long the export will take and updates it as they change. That estimate is what makes the controls meaningful: on the same 30-second animation, 720p WebM reads about 8 s while 1080p60 GIF reads about 1 min 40 s — a difference worth seeing before pressing the button rather than after.
The first estimate comes from a measured cost model, and the two formats behave nothing alike: video is almost flat in resolution (the work is rasterising the DOM once, plus a hardware encoder), while GIF is linear in pixels (quantisation and LZW run in JavaScript). After one export the player uses what this machine actually did instead of the model, so later estimates are sharper.
It does not interrupt playback
The export mounts a second player off screen and walks it through virtual
time with clock.seek. The player on screen keeps playing, at its own speed,
while the file is being written — and the export never waits for real time, so
it finishes in a fraction of the animation's own duration (roughly a tenth to a
third of it across the demos on this site). A progress bar and a Cancel
button sit in the menu while it runs.
Choosing a format
- WebM is the fastest and the smallest. It plays in Chrome, Firefox and Edge — but not in Safari, and not in PowerPoint.
- MP4 plays everywhere: Safari, slide decks, social platforms, chat apps. Reach for it whenever the file is going to be handed to someone else.
- GIF auto-plays where a video will not — a GitHub README, a Markdown file. It defaults to a smaller frame and a lower rate, as the format usually does. These diagrams are flat colour, so the 256-colour palette costs nothing visible.
Exporting needs the WebCodecs API for WebM and MP4 (Chromium, Firefox and
recent Safari); GIF does not. The encoders are loaded on demand, so a player
that never exports downloads none of that code. Has no effect if controls is
false.
Localising the chrome (labels)
The control bar and the JSON dialog carry user-visible strings — the buttons'
aria-labels and titles (tooltips) and the dialog's title — in English
by default. The labels prop overrides them key by key; any key you leave out
keeps its English default:
<DataFlowPlayer
spec={spec}
exportable
labels={{
play: 'Lecture',
pause: 'Pause',
prevStep: 'Étape précédente',
nextStep: 'Étape suivante',
jsonSpec: 'Spécification JSON',
}}
/>
The full key list is the exported PlayerLabels type: restart, play,
pause, prevStep, nextStep, progressBar, fullscreen,
exitFullscreen, jsonSpec, download, copy, copied, copyToClipboard,
close, closeDialog, loading.
loading is the odd one out: it is not part of the chrome but of the
placeholder the player shows before it mounts, and which reveals a loading
indicator when the wait lasts long enough to be worth naming — see
SSR and hydration. It lives here for
the same reason as the others: so the default stays in the core rather than in
each binding.
These strings live in attributes read by tooltips and assistive technology —
none of them is drawn on the stage. This site passes labels on every player
it renders, which is why the chrome follows the page language on the French
pages.
Custom icons
The icon field of a node first resolves
to a known tech, then to a registered badge. Two functions
feed these registries:
type IconSource = string | (() => SVGElement);
registerNodeIcon(type: string, icon: IconSource): void; // main node pictogram
registerSubIcon(name: string, icon: IconSource): void; // `icon` badge (tech)
An icon is SVG markup, or a factory returning an SVGElement when it has
to vary:
import { registerSubIcon } from '@dataflow-animator/react';
registerSubIcon(
'k8s',
'<svg viewBox="0 0 24 24" fill="#326CE5"><path d="…" /></svg>'
);
// then: { id: 'orchestrator', type: 'cloud', icon: 'k8s' }
// a factory, called on every resolution
registerSubIcon('build', () => buildAnimatedGlyph());
Markup is parsed once, on first use, and cloned afterwards. A registration always
wins over the built-in icon of the same name — including the stateful switch
and push_button pictograms.
registerNodeIcon / registerSubIcon mutate a module-level registry,
shared across all player instances and across requests in
SSR environments. Call them only once at application startup
(entry file, _app.tsx, layout.tsx...), never in a component body
or a useEffect. Registering never touches the DOM, so it is safe at module
scope in a bundle that also runs on the server.
The getNodeIcon(type) / getSubIcon(name) readers allow you to inspect the
registry if needed; both return a fresh SVGElement.
Syntax highlighting
By default, code (set_content in code mode, packet bodies) is highlighted
by Prism, via the exported highlightCode function:
highlightCode: (code: string, language: string) => string; // type Highlighter
You can entirely replace it using the highlight prop — useful to
plug in Shiki, Highlight.js, or avoid bundling Prism:
const myHighlighter: Highlighter = (code, language) =>
myEngine.toHtml(code, language);
<DataFlowPlayer spec={spec} highlight={myHighlighter} />;
The function receives the raw code and the declared language, and returns HTML.
escapeHtml is also exported as a fallback without highlighting.
Advanced API (engine)
For custom integrations (custom rendering, tests, tooling), the pure engine is exported. Its surface is low level and may evolve more freely than the component:
| Export | Role |
|---|---|
compile(spec) | Compiles a spec into a Timeline (ordered clips). |
evaluate(timeline, t) | Pure visual state at time t (ms). |
stepIndexAt / nextStop / prevStop | Step-by-step navigation and breakpoints. |
computeLayout(spec) | Relative node positions (ratios), without DOM. |
These functions touch no DOM and can run on the server. The player's clock is
not a React hook: it lives in the core as createPlayerClock, exported for
hosts that drive mountStage themselves.