Appearance
FlowCanvas theming
The values listed below are exposed as --rozie-flow-* custom properties, each with a built-in fallback, so FlowCanvas works with zero configuration and re-skins by remapping tokens. The structural rules compile per-leaf and are not consumer-overridable.
css
:root {
--rozie-flow-accent: #3b82f6;
--rozie-flow-bg: #f7f8fa;
--rozie-flow-node-bg: #ffffff;
--rozie-flow-node-body-padding: 0.5rem 0.75rem;
}Tokens
accent — the shared selected/active color.
| Token | Default |
|---|---|
--rozie-flow-accent | #3b82f6 |
canvas surface
| Token | Default |
|---|---|
--rozie-flow-bg | #f7f8fa |
--rozie-flow-grid-dot-color | rgba(0, 0, 0, 0.08) |
--rozie-flow-grid-size | 20px |
--rozie-flow-border-color | rgba(0, 0, 0, 0.1) |
--rozie-flow-radius | 8px |
node chrome
| Token | Default |
|---|---|
--rozie-flow-node-bg | #ffffff |
--rozie-flow-node-border | rgba(0, 0, 0, 0.16) |
--rozie-flow-node-radius | 8px |
--rozie-flow-node-shadow | 0 2px 6px rgba(0, 0, 0, 0.12) |
--rozie-flow-node-title-fg | #1f2937 |
--rozie-flow-node-selected-ring | rgba(59, 130, 246, 0.5) |
--rozie-flow-node-selected-shadow | 0 2px 8px rgba(0, 0, 0, 0.15) |
D-09: a node editor has real full-bleed body cases — an image, a sparkline, a nested chart — where the consumer needs 0 padding; a hardcoded value leaves them fighting it with a negative margin. Default matches .rozie-flow-node__title's padding so body and title line up.
| Token | Default |
|---|---|
--rozie-flow-node-body-padding | 0.5rem 0.75rem |
ports + sockets
D-15: under box-sizing: border-box this token now means the socket's RENDERED diameter (the 2px border sits inside it, not added on top). The default render is unchanged from the previous 12px content-box value — only the ~2px centring error is corrected. A consumer who previously hand-set 12px now gets a genuinely 12px socket; see the phase changeset.
| Token | Default |
|---|---|
--rozie-flow-port-fg | #6b7280 |
--rozie-flow-socket-size | 16px |
--rozie-flow-socket-bg | #94a3b8 |
--rozie-flow-socket-border-color | #ffffff |
--rozie-flow-socket-border-width | 2px |
--rozie-flow-socket-ring | rgba(0, 0, 0, 0.2) |
D-11 (ISSUE-6): opacity-only, deliberately carrying no colour — a colour-based hint would need an entry in all three hand-maintained dark palette copies, the very triplicate that produced both ISSUE-3 and A4. Opacity is immune to that drift, reads as "unavailable" rather than "error", and works against any consumer accent. Light-only by design; adds ZERO dark-block entries.
| Token | Default |
|---|---|
--rozie-flow-socket-incompatible-opacity | 0.3 |
connections
| Token | Default |
|---|---|
--rozie-flow-connection-stroke | #64748b |
--rozie-flow-connection-width | 3px |
--rozie-flow-connection-selected-width | 4px |
--rozie-flow-connection-label-fg | #334155 |
--rozie-flow-connection-label-halo | #ffffff |
built-in Controls overlay
| Token | Default |
|---|---|
--rozie-flow-control-bg | #ffffff |
--rozie-flow-control-fg | #334155 |
--rozie-flow-control-border | rgba(0, 0, 0, 0.16) |
--rozie-flow-control-radius | 6px |
--rozie-flow-control-shadow | 0 1px 3px rgba(0, 0, 0, 0.14) |
--rozie-flow-control-hover-bg | #f1f5f9 |
--rozie-flow-control-active-bg | #e2e8f0 |
--rozie-flow-control-selected-bg | #dbeafe |
--rozie-flow-control-selected-fg | #1d4ed8 |
--rozie-flow-control-inset | 10px |
--rozie-flow-control-btn-size | 28px |
marquee (mode:'select')
D-25: --rozie-flow-marquee-bg derives off --rozie-flow-accent in BOTH light and dark, so overriding accent alone recolors the marquee too — zero dark-block entries needed. The derivation lives at the component's read site (color-mix(in srgb, var(--rozie-flow-accent) 12%, transparent)), not here, so it is resolved against the accent the canvas actually inherits. color-mix() needs an evergreen browser; where it does not parse, the marquee background is transparent.
| Token | Default |
|---|---|
--rozie-flow-marquee-radius | 2px |
MiniMap overlay
| Token | Default |
|---|---|
--rozie-flow-minimap-bg | rgba(255, 255, 255, 0.82) |
--rozie-flow-minimap-border | rgba(0, 0, 0, 0.16) |
--rozie-flow-minimap-shadow | 0 1px 3px rgba(0, 0, 0, 0.14) |
The two tokens below are read from JS via a getComputedStyle-backed flowToken() lookup at render time (canvas paint code, not a CSS var()) — declared here (and correctly base-only per the probe) so the public surface stays discoverable in one place.
| Token | Default |
|---|---|
--rozie-flow-minimap-node-fill | #94a3b8 |
--rozie-flow-minimap-mask | rgba(15, 23, 42, 0.18) |
resize handle (the four corner drag affordances on the canvas)
| Token | Default |
|---|---|
--rozie-flow-resize-handle-bg | #ffffff |
--rozie-flow-resize-handle-size | 8px |
--rozie-flow-resize-handle-radius | 2px |
NodeToolbar overlay
| Token | Default |
|---|---|
--rozie-flow-toolbar-bg | #ffffff |
--rozie-flow-toolbar-border | rgba(0, 0, 0, 0.16) |
--rozie-flow-toolbar-shadow | 0 2px 8px rgba(0, 0, 0, 0.18) |
--rozie-flow-toolbar-btn-bg | #f8fafc |
--rozie-flow-toolbar-btn-fg | #334155 |
--rozie-flow-toolbar-btn-border | rgba(0, 0, 0, 0.14) |
--rozie-flow-toolbar-btn-hover-bg | #eef2f7 |
--rozie-flow-toolbar-delete-fg | #b91c1c |
typography
Splitting the family out from the four per-role sizes means rebranding is ONE override, while each role's size stays independently tunable — the two knobs solve different problems and shouldn't be conflated.
| Token | Default |
|---|---|
--rozie-flow-font-family | system-ui, sans-serif |
--rozie-flow-node-font-size | 13px |
--rozie-flow-control-font-size | 16px |
--rozie-flow-toolbar-font-size | 12px |
--rozie-flow-connection-label-font-size | 11px |
focus ring
The ring's colour, --rozie-flow-focus-ring, defaults to --rozie-flow-accent at the read site, so the ring follows dark and any accent override with ZERO entries in the three hand-maintained dark palettes. Do not "helpfully" hardcode a colour for it — that would reintroduce exactly the drift D-20's predicate exists to catch.
| Token | Default |
|---|---|
--rozie-flow-focus-ring-width | 2px |
Defaulted where FlowCanvas reads them
Public, but declared nowhere by base.css: the fallback below applies only while no scope sets the token, and it is resolved at the component itself — so it follows a value set on any ancestor (a token it defaults to included).
| Token | Default |
|---|---|
--rozie-flow-connection-selected-stroke | var(--rozie-flow-accent, #3b82f6) |
--rozie-flow-control-selected-border | var(--rozie-flow-accent, #3b82f6) |
--rozie-flow-focus-ring | var(--rozie-flow-accent, #3b82f6) |
--rozie-flow-marquee-bg | color-mix(in srgb, var(--rozie-flow-accent, #3b82f6) 12%, transparent) |
--rozie-flow-marquee-border | var(--rozie-flow-accent, #3b82f6) |
--rozie-flow-node-selected-border | var(--rozie-flow-accent, #3b82f6) |
--rozie-flow-resize-handle-border | var(--rozie-flow-accent, #3b82f6) |
--rozie-flow-socket-hover-bg | var(--rozie-flow-accent, #3b82f6) |
Design-system bridges
Each package ships token presets that map FlowCanvas's tokens onto a known design system's published CSS variables — import base.css first, then a bridge:
ts
import '@rozie-ui/rete-react/themes/base.css';
import '@rozie-ui/rete-react/themes/bootstrap.css';
import '@rozie-ui/rete-react/themes/material.css';
import '@rozie-ui/rete-react/themes/shadcn.css';See also
- FlowCanvas — showcase & API — the full prop / event / slot / handle reference.
- FlowCanvas — usage examples — idiomatic per-framework consumption code.
- FlowCanvas comparison — how it stacks up against the per-framework libraries.
- FlowCanvas — live demo — the real package running in the page.