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

@sonata-innovations/fiber-fbre

v3.3.0

Published

Fiber Render Engine — renders Flow JSON forms with conditional logic, validation, and screen transitions

Readme

@sonata-innovations/fiber-fbre v2

Fiber Render Engine — consumes Flow JSON and renders data collection forms. Handles conditional logic, validation, screen transitions, and outputs collected FlowData back to the parent application.

Quick Start

Local Mode

Pass a Flow object directly:

import { FBRE } from "@sonata-innovations/fiber-fbre";
import "@sonata-innovations/fiber-fbre/styles";

function App() {
  return (
    <FBRE
      flow={myFlow}
      screenIndex={0}
      onFlowComplete={(data) => console.log(data)}
    />
  );
}

Remote Mode

Fetch a published flow from a Fiber API:

import { FBRE } from "@sonata-innovations/fiber-fbre";
import "@sonata-innovations/fiber-fbre/styles";

function App() {
  return (
    <FBRE
      flowId="your-flow-id"
      apiEndpoint="https://your-api.example.com/api/v1"
      apiKey="your-api-key"
      onFlowComplete={(data) => console.log(data)}
    />
  );
}

Server-Driven Mode

Session-based rendering where the server evaluates conditions and validation:

import { FBRE } from "@sonata-innovations/fiber-fbre";
import "@sonata-innovations/fiber-fbre/styles";

function App() {
  return (
    <FBRE
      sessionEndpoint="https://your-api.example.com/api/v1/public/sessions"
      flowId="your-flow-id"
      onFlowComplete={(data) => console.log(data)}
    />
  );
}

Installation

npm install @sonata-innovations/fiber-fbre

Then import the CSS in your app entry point:

import "@sonata-innovations/fiber-fbre/styles";

Peer dependencies: react ^18.0.0 || ^19.0.0, react-dom ^18.0.0 || ^19.0.0

Runtime dependencies: zustand, @sonata-innovations/fiber-types, @sonata-innovations/fiber-shared

API

<FBRE /> Props

FBRE supports three rendering modes, selected by which props you pass. The modes are mutually exclusive.

Local Mode

Render a Flow object you already have in memory.

| Prop | Type | Required | Description | |------|------|----------|-------------| | flow | Flow | Yes | Flow JSON object | | data | FlowData | No | Pre-populated form data | | mode | FlowModeType | No | Override form mode ("standard" | "conversational") | | screenIndex | number | No | Initial screen index (default 0) | | theme | ThemeConfig | No | Override theme settings (merged over flow.config.theme) | | navigation | NavigationConfig | No | Override navigation settings (merged over flow.config.navigation) | | controls | ControlsConfig | No | Override controls settings (merged over flow.config.controls) | | context | Record<string, string \| boolean \| number> | No | External context values for condition evaluation and calculations | | storeRef | MutableRefObject<StoreApi<FBREStoreState> \| null> | No | Ref to access the Zustand store | | onFlowComplete | (data: FlowData) => void \| ConfirmationResult \| Promise<void \| ConfirmationResult> | Yes | Called when the user completes the flow. Return/resolve a ConfirmationResult to override the configured confirmation message; a rejected Promise surfaces a completion error | | onScreenChange | (index: number, data: FlowData) => void | No | Called on screen navigation | | onScreenValidationChange | (index: number, data: any) => void | No | Called when screen validity changes |

Remote Mode

Fetch a published flow from a Fiber API by ID.

| Prop | Type | Required | Description | |------|------|----------|-------------| | flowId | string | Yes | ID of the published flow | | apiEndpoint | string | Yes | Base URL of the Fiber API | | apiKey | string | No | API key for authentication | | data | FlowData | No | Pre-populated form data | | mode | FlowModeType | No | Override form mode ("standard" | "conversational") | | screenIndex | number | No | Initial screen index (default 0) | | theme | ThemeConfig | No | Override theme settings | | navigation | NavigationConfig | No | Override navigation settings | | controls | ControlsConfig | No | Override controls settings | | context | Record<string, string \| boolean \| number> | No | External context values for condition evaluation and calculations | | storeRef | MutableRefObject<StoreApi<FBREStoreState> \| null> | No | Ref to access the Zustand store | | onFlowComplete | (data: FlowData) => void \| ConfirmationResult \| Promise<void \| ConfirmationResult> | Yes | Called when the user completes the flow. Return/resolve a ConfirmationResult to override the configured confirmation message; a rejected Promise surfaces a completion error | | onScreenChange | (index: number, data: FlowData) => void | No | Called on screen navigation | | onScreenValidationChange | (index: number, data: any) => void | No | Called when screen validity changes |

Server-Driven Mode

Session-based rendering. The server evaluates conditions and validation; the client renders one screen at a time.

| Prop | Type | Required | Description | |------|------|----------|-------------| | sessionEndpoint | string | Yes | Session API base URL | | flowId | string | Yes | ID of the flow to start a session for | | apiKey | string | No | API key for authentication | | mode | FlowModeType | No | Override form mode ("standard" | "conversational") | | theme | ThemeConfig | No | Override theme settings | | context | Record<string, string \| boolean \| number> | No | External context values for condition evaluation and calculations | | onFlowComplete | (data: FlowData) => void | Yes | Called when the user completes the flow | | onScreenChange | (screenNumber: number) => void | No | Called on screen navigation (note: receives screen number, not index + data) |

Imperative Access

Access the store directly for programmatic control:

import { useFBREStore, useFBREStoreApi, useFBREApi } from "@sonata-innovations/fiber-fbre";

// Inside a child of <FBRE />
const flowData = useFBREStore((s) => s.getFlowData());
const storeApi = useFBREStoreApi();
const data = storeApi.getState().getFlowData();

// In remote mode — access API config, flowId, tenantId
const apiContext = useFBREApi();

Events

import { addFBREEventListener, removeFBREEventListener } from "@sonata-innovations/fiber-fbre";

const handler = (id, data) => console.log("file uploaded:", id, data);
addFBREEventListener("file-upload", handler);
removeFBREEventListener("file-upload", handler);

Exports

// Components
import { FBRE } from "@sonata-innovations/fiber-fbre";

// Store hooks
import { useFBREStore, useFBREStoreApi, useFBREApi } from "@sonata-innovations/fiber-fbre";

// Event system
import { addFBREEventListener, removeFBREEventListener } from "@sonata-innovations/fiber-fbre";

// Error classes
import { ApiError, TimeoutError } from "@sonata-innovations/fiber-fbre";

// Types
import type {
  FBREProps,
  FBRELocalProps,
  FBRERemoteProps,
  FBREServerDrivenModeProps,
  FBREStoreState,
  FBREApiConfig,
  Flow,
  FlowScreen,
  FlowConfiguration,
  ThemeConfig,
  NavigationConfig,
  ControlsConfig,
  ConfirmationConfig,
  ConfirmationResult,
  FlowMetadata,
  Component,
  ComponentProperties,
  ComponentDisplayProperties,
  ComponentDividerProperties,
  ComponentInputProperties,
  ComponentOptionProperties,
  ComponentSliderProperties,
  ComponentSwitchProperties,
  ComponentRatingProperties,
  ComponentFileUploadProperties,
  ComponentGroupProperties,
  FlowData,
  ScreenData,
  ComponentData,
  FileUploadData,
  FileUploadBase64Data,
  FileUploadS3Data,
  FlowConditionConfig,
  ConditionOperator,
  ConditionRule,
  ConditionGroup,
  ConditionDependency,
} from "@sonata-innovations/fiber-fbre";

Confirmation Screen

A terminal "thank you" screen shown after submission, configured on the flow (config.confirmation) — not a Screen. Renders only when show is not false and title/body has content, replacing the final screen and hiding the controls/stepper. title/body support ${...} reference markup (collected fields, calculations, and context values by name).

const flow = {
  ...myFlow,
  config: { ...myFlow.config, confirmation: { show: true, title: "Thank you!", body: "Submitted, ${email}." } },
};
<FBRE flow={flow} onFlowComplete={handleComplete} />

onFlowComplete decides when it shows: return void → shows immediately; return a Promise → the submit button stays in-flight until it settles (resolve shows it, reject shows a completion error instead); return/resolve a ConfirmationResult ({ title?, body? }) → overrides the configured message, e.g. with a server reference number.

const handleComplete = async (data: FlowData): Promise<ConfirmationResult> => {
  const { referenceId } = await saveToBackend(data);
  return { title: "All done!", body: `Reference: ${referenceId}.` };
};

Flow JSON Schema

Flow
├── uuid: string
├── metadata: { name?, description?, ...}
├── config?: { mode?, theme?: { color?, colorScheme?, style?, background?, surface?, text?, border?, radius?, fontFamily?, error?, success?, warning? }, navigation?: { transition?, allowInvalidTransition? }, controls?: { show?, layout?, showStepper? }, summary?, confirmation?: { show?, title?, body? } }
└── screens: FlowScreen[]
    ├── uuid: string
    ├── label?: string
    ├── conditions?: FlowConditionConfig
    └── components: Component[]
        ├── uuid: string
        ├── type: string
        ├── properties: { ... }
        ├── conditions?: FlowConditionConfig
        └── components?: Component[]  (groups and repeaters)

Component Types

| Type | Category | Description | |------|----------|-------------| | header | Display | Heading text | | text | Display | Rich text with markup support | | divider | Display | Horizontal rule with optional label | | callout | Display | Styled alert/info box with variant coloring (info, success, warning, neutral) | | table | Display | Static comparison/data table with optional column highlighting | | inputText | Input | Single-line text field | | inputTextArea | Input | Multi-line text field | | inputNumber | Input | Numeric input (supports decimal restriction and start adornment) | | dropDown | Selection | Native select dropdown | | dropDownMulti | Selection | Multi-select with tag chips | | checkbox | Selection | Checkbox group | | radio | Selection | Radio button group | | toggleSwitch | Selection | On/off toggle | | yesNo | Selection | Two large tappable buttons for binary yes/no | | cardSelect | Selection | Card-based visual selection with image/icon support | | date | Date & Time | Calendar popup date picker | | time | Date & Time | Hour/minute time selector | | dateTime | Date & Time | Combined date + time picker | | dateRange | Date & Time | Two date pickers for start/end | | timeRange | Date & Time | Two time pickers for start/end | | dateTimeRange | Date & Time | Two datetime pickers for start/end | | fileUpload | Interactive | File picker with size validation | | rating | Interactive | Star rating with half-star precision | | slider | Interactive | Range slider with value label | | colorPicker | Interactive | Saturation/hue picker with hex input | | computed | Display | Calculated field with formula-based computed values | | signature | Interactive | Signature pad — draw on canvas, type name, or both | | group | Container | Component container with layout grid, collapsible | | repeater | Container | Iterable component container (add/remove iterations, pre-populated rows) |

Conditions

Components and screens can be conditionally shown/hidden using FlowConditionConfig:

{
  "action": "show",
  "when": {
    "logic": "and",
    "rules": [
      {
        "source": "other-component-uuid",
        "operator": "equals",
        "value": "yes"
      }
    ]
  }
}

Logic: "and" (all rules must match) or "or" (any rule matches).

Context rules: Set sourceType: "context" on a rule to reference an external context value instead of a component. In this case, source is a context key (not a component UUID).

Operators:

| Category | Operators | |----------|-----------| | Equality | equals, notEquals | | String | contains, notContains, startsWith, endsWith | | Presence | isEmpty, isNotEmpty | | Numeric | greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual | | Set (single) | isOneOf, isNotOneOf | | Set (multi) | includesAny, includesAll, includesNone | | Boolean | isTrue, isFalse |

Condition results are stored separately from the Flow JSON in a conditionResults map (keyed by target UUID). Hidden components are excluded from FlowData output.

Validation

Components support a rules-based validation system via properties.validation:

{
  "validation": {
    "rules": [
      { "type": "required" },
      { "type": "email" },
      { "type": "minLength", "params": { "min": 5 } }
    ]
  }
}

17 built-in validators: required, email, phone, url, minLength, maxLength, exactLength, minValue, maxValue, pattern, minSelected, maxSelected, fileType, fileSize, contains, excludes, matchesField

Each validator has a default error message. Custom messages can be set per rule via the message field.

Error display: The first failing message is shown below the field with an "(and N more)" count when multiple rules fail.

Cross-field validation: matchesField enables bidirectional re-evaluation — changing either field re-validates the other.

Migration: Legacy required and regex flat properties are automatically converted to validation rules on load.

Validation errors are stored in validationErrors: Record<string, string[]> on the store (keyed by component UUID). Hidden components (by conditions) do not block screen validity.

Calculations

Flows can include formula-based computed values via FlowCalculation:

{
  "calculations": [
    {
      "uuid": "calc-uuid",
      "formula": "{{comp-a-uuid}} + {{comp-b-uuid}} * 0.1",
      "label": "Total"
    }
  ]
}
  • Formulas reference component values via {uuid} tokens and support standard arithmetic (+, -, *, /)
  • Aggregation functions: SUM(), COUNT(), AVG(), MIN(), MAX() over repeater iterations
  • Scalar MIN(expr, ...) / MAX(expr, ...) for capping values (e.g. MIN({discount}, 25))
  • IF(condition, then, else) for conditional formulas (e.g. tiered pricing)
  • Comparison operators: >, <, >=, <=, ==, != (return 1/0)
  • Option metadata is accessible for option-based components
  • Results update reactively when source values change
  • Access results via getCalculationResult(uuid) on the store

Markup

Text components and detail fields support inline markup:

  • [b]bold[/b]bold
  • [i]italic[/i]italic
  • [l href="url"]link[/l]link

Conversational Mode

Set mode: "conversational" to transform FBRE into a one-question-per-screen experience:

// Via JSX prop
<FBRE flow={myFlow} mode="conversational" onFlowComplete={handleComplete} />

// Via flow config
const flow = {
  ...myFlow,
  config: { ...myFlow.config, mode: "conversational" }
};
<FBRE flow={flow} onFlowComplete={handleComplete} />

What changes:

  • Content is vertically and horizontally centered
  • Single-select components (radio, yesNo, cardSelect, dropDown) auto-advance ~500ms after selection
  • Pressing Enter on inputText / inputNumber advances to the next screen
  • Auto-advance (both Enter and on-change) only fires when the screen renders exactly one interactive component. Multi-input screens — including those produced by FBTL's per-card page-break toggle — behave like a normal form: the user fills every field and clicks Continue. Display-only components (text, header, callout, divider, table, computed) and components hidden by conditions don't count toward the input total.
  • Components animate in with fade + scale + stagger on screen transitions (respects prefers-reduced-motion)
  • Option items, Yes/No buttons, card-select cards, and input fields are enlarged for easier tapping
  • Each of the 6 existing styles has a tailored conversational presentation (e.g. clean = bordered cards, soft-outlined = pill-shaped options, defined-outlined = inverted selection)

What doesn't change:

  • FlowData output is identical
  • Conditions, validation, calculations all work the same
  • Navigation buttons still work alongside auto-advance
  • Works with all 6 transition types and dark mode

Theming

FBRE's appearance is driven entirely by CSS custom properties (the --fbre-* tokens). There are two independent axes — style controls the shape (borders vs. underlines, fills, spacing; set via theme.style), and the palette controls the colors. This section is a complete reference for the palette.

Palette values resolve through four layers, each overriding the one above, so you only specify what you want to change:

1.  Light defaults                  base.css                       (lowest)
2.  Preset:  colorScheme: "dark"    base.css [data-mode="dark"]
3.  Knobs:   theme.{surface,text…}  inline custom properties
4.  Raw CSS: your own --fbre-* rules your stylesheet               (highest)

Color scheme (presets)

theme.colorScheme ("light" default | "dark") seeds the entire token set with a coherent light or dark palette and sets data-mode on the container. Set it on the theme prop or in the flow config (the prop takes precedence):

<FBRE flow={myFlow} theme={{ colorScheme: "dark" }} onFlowComplete={handleComplete} />

Replaces the former darkMode: boolean: darkMode: truecolorScheme: "dark", darkMode: false/absent → colorScheme: "light". There is no runtime fallback — migrate stored flows before upgrading.

Theme knobs

Layer brand colors on top of the preset with the theme palette knobs. Each knob sets its primary --fbre-* token plus the tokens derived from it (e.g. surface also drives the input fills and hover/alt surfaces, via mode-aware color-mix). Any subset may be set; unset knobs fall through to the preset.

<FBRE
  flow={myFlow}
  theme={{
    colorScheme: "dark",
    color: "#bcd3cd",       // accent
    background: "#1a2a3a",  // form ground
    surface: "#243244",     // input fills / cards
    text: "#e8ede9",
    border: "#2e4158",
    radius: "3px",
    fontFamily: '"Inter", system-ui, sans-serif',
  }}
  onFlowComplete={handleComplete}
/>

| Knob | Primary token | Also derives | | --- | --- | --- | | color | --fbre-theme-color | light/dark accent tints | | background | --fbre-bg | — | | surface | --fbre-surface | input fills, hover/alt/subtle surfaces, control surface | | text | --fbre-text | secondary/placeholder/disabled, label | | border | --fbre-border | hover/light/subtle | | radius | --fbre-radius | — | | fontFamily | --fbre-font | — | | error / success / warning | --fbre-{error,success,warning} | their -bg / -border (and error-light) |

Raw CSS (escape hatch)

For tokens not exposed as a knob (e.g. --fbre-star-filled, --fbre-shadow-color) — or for surgical control — override the --fbre-* variables directly. Scope to your wrapper so you out-specify the engine defaults regardless of load order:

.my-form-wrapper .fbre-container {
  --fbre-surface: #243244;
  --fbre-star-filled: #bcd3cd;
}

Knobs are inline (highest specificity among engine-supplied values), so a knob and a raw rule on the same token: the knob wins unless your selector is also high-specificity. Use knobs for the common palette, raw CSS for the long tail.

Token catalog

Every themeable value, with light/dark preset defaults. Tokens marked (derived) are produced from a knob when it's set; you can also set any of them raw.

Accent--fbre-theme-color (#1976d2 / #42a5f5, knob color), --fbre-theme-light (#e3f2fd / #1a2332, derived), --fbre-theme-dark (#1565c0 / #1e88e5, derived), --fbre-on-primary (#fff / #fff).

Ground & surfaces

| Token | Light | Dark | Knob | | --- | --- | --- | --- | | --fbre-bg | #fff | #121212 | background | | --fbre-surface | #fff | #1e1e1e | surface | | --fbre-surface-hover | #f5f5f5 | #2a2a2a | (derived) | | --fbre-surface-alt | #e0e0e0 | #333 | (derived) | | --fbre-surface-subtle | #e8e8e8 | #2a2a2a | (derived) | | --fbre-control-surface | #fff | #e0e0e0 | (derived) | | --fbre-input-bg | var(--fbre-surface) | var(--fbre-surface) | (derived) | | --fbre-input-bg-focus | var(--fbre-bg) | var(--fbre-bg) | (derived) |

Text--fbre-text (#212121 / #e0e0e0, knob text), --fbre-text-secondary (#666 / #9e9e9e, derived), --fbre-text-placeholder (#aaa / #666, derived), --fbre-text-disabled (#bbb / #555, derived), --fbre-label (var(--fbre-text-secondary), derived).

Borders--fbre-border (#ccc / #444, knob border), --fbre-border-hover (#999 / #666, derived), --fbre-border-light (#ddd / #444, derived), --fbre-border-subtle (#eee / #333, derived).

State colors

| Token | Light | Dark | Knob | | --- | --- | --- | --- | | --fbre-error | #d32f2f | #ef5350 | error | | --fbre-error-light | #fdecea | #2c1b1b | (derived) | | --fbre-success | #2d6a28 | #a8e6a0 | success | | --fbre-success-bg | #ddffda | #1a3318 | (derived) | | --fbre-success-border | #b8e6b4 | #2d5a28 | (derived) | | --fbre-warning | #7a5900 | #ffe082 | warning | | --fbre-warning-bg | #fff8e1 | #332b00 | (derived) | | --fbre-warning-border | #ffe082 | #665500 | (derived) |

Controls (no knob — set raw)--fbre-toggle-track (#ccc / #555), --fbre-star-empty (#ddd / #444), --fbre-star-filled (#ffc107 / #ffca28), --fbre-slider-track (#ddd / #444), --fbre-shadow-color (rgba(0,0,0,.12) / rgba(0,0,0,.4)).

Shape & motion--fbre-radius (4px, knob radius), --fbre-font ("Segoe UI", system-ui, sans-serif, knob fontFamily), --fbre-layout-gap (4px, varies by style), --fbre-transition-duration (250ms), --fbre-transition-easing (cubic-bezier(0.4, 0, 0.2, 1)).

Screen Transitions

Animate screen changes by setting navigation.transition in the flow config:

{
  "config": {
    "navigation": { "transition": "slideFade" }
  }
}

| Type | Effect | Direction-aware? | |------|--------|-----------------| | "none" | Instant swap (default) | — | | "slide" | Full horizontal slide | Yes | | "fade" | Crossfade | No | | "slideFade" | 30px slide + opacity | Yes | | "rise" | Vertical rise/sink | Yes | | "scaleFade" | Scale + opacity | No |

Direction-aware transitions reverse when navigating backward. Buttons are disabled during animation to prevent overlapping transitions.

Customize timing via CSS custom properties:

.fbre-container {
  --fbre-transition-duration: 250ms;
  --fbre-transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
}

@media (prefers-reduced-motion: reduce) sets duration to 0ms automatically.

Architecture

| Concern | Implementation | |---------|---------------| | UI framework | Zero-dependency custom CSS | | Styling | CSS custom properties + BEM (fbre- prefix) | | Display detection | isDisplayComponent() checks type against constant set (header, text, divider, callout, table) | | State | Zustand store factory, one store per <FBRE /> instance via React Context | | Re-renders | Zustand selectors — granular, automatic | | Build | Vite library mode | | Components | Registry pattern (Map<string, FC>), one file per type | | Dependencies | react, zustand, @sonata-innovations/fiber-types, @sonata-innovations/fiber-shared |

FlowData Output

getFlowData() returns collected form data. Display-only components (header, text, divider, callout, table) are excluded automatically based on their type.

FlowData
├── uuid: string
├── metadata: Record<string, string>
└── screens: ScreenData[]
    ├── uuid: string
    ├── label?: string
    └── components: ComponentData[]
        ├── uuid: string
        ├── type: string
        ├── label?: string          (omitted when empty)
        ├── value: string | string[] | number | number[] | boolean | null | FileUploadData
        └── components?: ComponentData[][]  (groups and repeaters)

Build

npm run build

Output:

dist/
├── index.d.ts        # Bundled type declarations
├── fiber-fbre.js     # ES module
├── fiber-fbre.cjs    # CommonJS
└── fiber-fbre.css    # All styles

Responsive Sizing

FBRE uses CSS container queries to automatically scale controls in narrow containers (under 400px). Buttons, stepper dots, and toggle switches shrink proportionally. No configuration needed — just embed in a smaller container and the compact sizing kicks in.

Multiple Instances

Each <FBRE /> creates its own isolated Zustand store. Multiple instances can run simultaneously on the same page without state conflicts.

<FBRE flow={flowA} onFlowComplete={handleA} />
<FBRE flow={flowB} onFlowComplete={handleB} />