npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flowlinejs/react

v0.3.0

Published

React Flow-based visual editor, run viewer and run list for Flowline workflows.

Downloads

463

Readme

@flowlinejs/react

The Flowline workflow editor, run viewer and run list for React. See the main README for the components and the provider's options.

Styles and cascade layers

Import the stylesheet once, next to your app's own CSS:

import "@flowlinejs/react/styles.css";

Every Flowline rule sits in the flowline cascade layer. Styles outside any layer beat styles in a layer, whatever their specificity, so your unlayered CSS always wins over Flowline's. That is what makes theming easy, and it's also the one trap to avoid.

Put element resets in a layer below flowline

A global reset such as

button, input, select, textarea { font: inherit; color: inherit; }

is unlayered, so it beats Flowline's .fl-btn--primary { color: var(--fl-accent-fg) } and the editor's buttons lose their colors (white text on the accent turns into body text on the accent). The same goes for a { color }, :focus-visible { outline } and similar element rules.

Put resets in a layer and declare the layer order before Flowline's stylesheet loads. The first place a layer is named fixes its order, so do this in a file imported first:

/* layers.css — import this before @flowlinejs/react/styles.css */
@layer reset, flowline;
/* your app's CSS */
@layer reset {
  button, input, select, textarea { font: inherit; color: inherit; }
  a { color: var(--brand); }
}

/* Class-level app styles can stay unlayered. */
.app-header { … }
import "./layers.css";
import "@flowlinejs/react/styles.css";
import "./app.css";

If you use a CSS framework that already has layers (Tailwind v4's base, for example), list flowline after its base layer: @layer theme, base, flowline, components, utilities;.

The mini-crm example does exactly this in examples/mini-crm/web/src/layers.css.

Theming

Prefer tokens to overriding rules. Pass theme={{ colorMode, tokens }} to <FlowlineProvider>, or set the --fl-* custom properties (--fl-accent, --fl-accent-fg, --fl-bg, --fl-surface, --fl-border, --fl-text, --fl-text-muted, --fl-danger, --fl-warning, --fl-success, --fl-radius, --fl-font, --fl-font-mono and others at the top of styles.css) in an unlayered .fl-root { … } rule. Flowline defines them on .fl-root itself, so setting them on an ancestor has no effect. Dark mode follows colorMode, or prefers-color-scheme when it is "system".

An unlayered .fl-root { --fl-accent: … } applies in both colour modes. For dark-only values, scope the rule to dark mode: .fl-root[data-fl-theme="dark"] { … }, plus @media (prefers-color-scheme: dark) { .fl-root[data-fl-theme="system"] { … } } if you use colorMode: "system". theme.tokens also applies in both modes, so pass mode-specific tokens from your own colour-mode state if you need them.

Annotations have their own palette. Each of the six colours (yellow, blue, green, pink, purple, gray) has three tokens: --fl-annot-<colour>-bg, -border and -text, for example --fl-annot-yellow-bg. In theme.tokens they are annotYellowBg, annotYellowBorder, annotYellowText and so on (the AnnotationToken type). Section regions and sticky notes fill with -bg. Regions are outlined with -border, which is also the left accent of a coloured step card. A section's header text uses -text. The built-in values keep -text and --fl-text at WCAG AA contrast (4.5:1) on -bg in both colour modes, so keep that in mind when you override them. A colour outside the six draws as gray. The ring that flashes around steps an agent just changed is --fl-changed. It is derived from --fl-accent, so it follows your accent.

import type { FlowlineTheme } from "@flowlinejs/react";

export const theme: FlowlineTheme = {
  tokens: { annotYellowBg: "#fff4c2", annotYellowText: "#5c4300" },
};

When you do override a Flowline rule, an unlayered rule with any specificity wins. You don't need !important.

Agent bridge

The bridge lets an AI agent edit the open workflow the way a person does. Its reads see the editor's current doc at call time, and its edits go through the editor store's apply, so each batch is one undo step, flashes the changed steps on the canvas, and is rejected with code "readOnly" while the editor is read-only. It serves the same tools as core's commandCatalog (see the core README); bridge.runTool(name, args) runs one call.

For an agent loop outside React, create a bridge per store with createAgentBridge in onStoreReady. The editor calls it for every store it creates (first load, a new workflowId, a retry, a new workflow), and runs the cleanup you return when that store is replaced or the editor unmounts:

import { commandCatalog, type Manifest, type ToolDefinition } from "@flowlinejs/core";
import { createAgentBridge, WorkflowEditor } from "@flowlinejs/react";

/** Your agent loop: it sends `tools` to the model and calls `run` for each tool call. */
interface Agent {
  attach(tools: ToolDefinition[], run: (name: string, args: unknown) => unknown): void;
  detach(): void;
}

export function AgentEditor({ agent, manifest }: { agent: Agent; manifest: Manifest }) {
  return (
    <WorkflowEditor
      workflowId="welcome-contact"
      onStoreReady={(store) => {
        const bridge = createAgentBridge(store);
        agent.attach(commandCatalog(manifest), (name, args) => bridge.runTool(name, args));
        return () => agent.detach();
      }}
    />
  );
}

Inside the editor, useWorkflowAgentBridge() returns the bridge of the enclosing <WorkflowEditor>'s or <WorkflowCanvas>'s store (or of the store you pass it), the same object until the store changes. bridge.read holds every read bound to the store, and bridge.apply(commands) runs a batch directly. A read is a snapshot taken when you call it, so subscribe to doc to recompute it after each edit:

import {
  ConfigPanel,
  type EditorStore,
  useEditorStore,
  useWorkflowAgentBridge,
  WorkflowEditor,
} from "@flowlinejs/react";
import { useMemo } from "react";

export function AgentPanel() {
  const bridge = useWorkflowAgentBridge();
  const doc = useEditorStore((s) => s.doc);
  // `doc` is a dependency so the read re-runs after each edit.
  const { text } = useMemo(() => bridge.read.overview({ budget: 2000 }), [bridge, doc]);
  const rename = () => bridge.apply([{ op: "renameWorkflow", name: "Welcome VIPs" }]);
  return (
    <section>
      <pre>{text}</pre>
      <button type="button" onClick={rename}>
        Rename
      </button>
    </section>
  );
}

// `renderPanel` replaces the default panel, so render the config form next to your own.
const panel = (store: EditorStore) => (
  <>
    <ConfigPanel store={store} />
    <AgentPanel />
  </>
);

export function AgentWorkflowPage() {
  return <WorkflowEditor workflowId="welcome-contact" renderPanel={panel} />;
}

The renderPanel slot shows only while a step is selected. For an agent panel that is always visible, render it in your own layout and give it a bridge from onStoreReady with createAgentBridge(store), as above.