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-fbtl

v3.2.0

Published

Fiber Tool Lite — end-user-friendly Flow builder for non-technical authors embedded in parent apps

Readme

@sonata-innovations/fiber-fbtl

Fiber Tool Lite — an end-user-friendly Flow builder for non-technical authors embedded in parent apps.

FBTL is a stripped-down alternative to @sonata-innovations/fiber-fbt with a minimal, hand-holding UX. Both tools produce the same Flow JSON schema, so any Flow is portable between them. FBTL is designed to be dropped into a parent application so end users (clinicians, teachers, etc.) can author their own forms without needing developer knowledge.

Install

npm install @sonata-innovations/fiber-fbtl

@sonata-innovations/fiber-types, @sonata-innovations/fiber-shared, and @sonata-innovations/fiber-fbre are direct dependencies and will be installed automatically. react and react-dom (v18 or v19) are required as peer dependencies in the host app.

Documentation

Full docs ship inside this package under docs/, so they land in node_modules/@sonata-innovations/fiber-fbtl/docs/ at the exact version you installed. AGENTS.md at the package root is the routing guide — start there when pointing an AI agent at the package. The same docs are browsable in the public mirror.

| Doc | In package | Browse | |-----|------------|--------| | Fiber concepts (Flow → Screen → Component) | docs/fiber-concepts.md | mirror | | FBTL integration guide | docs/integration/fbtl.md | mirror | | Flow JSON schema | ships in @sonata-innovations/fiber-types under docs/schema/ | schema · quick ref |

Usage

import { FBTL } from "@sonata-innovations/fiber-fbtl";
import "@sonata-innovations/fiber-fbtl/styles";
import type { Flow } from "@sonata-innovations/fiber-fbtl";

function App() {
  const [flow, setFlow] = useState<Flow>(initialFlow);
  return <FBTL flow={flow} onChange={setFlow} />;
}

FBTL is a controlled component — store the object handed to onChange and pass it straight back as flow, exactly like a controlled <input>. The useState + onChange={setFlow} form above is correct and loop-free. If you instead route the flow through a layer that re-creates the object on the way back (a reducer/normalizer, deep-clone, setFlow({ ...next }), React Query cache, or form library), be aware of the feedback loop it can cause: see Controlled-component contract.

Props

| Prop | Type | Description | |---|---|---| | flow | Flow | Required. Current flow. Controlled — parent owns state. | | onChange | (flow: Flow) => void | Called on every mutation. Parent persists. | | storeRef | MutableRefObject<StoreApi<FBTLStoreState> \| null> | Receives the internal Zustand store API once mounted. Lets siblings outside the <FBTL> subtree read state via useFBTLStore(storeRef.current, selector) or storeRef.current.getState(). Mirrors <FBT storeRef> / <FBRE storeRef>. | | themeDefaults | ThemeConfig | Passed to the preview pane, merged under flow.config.theme — the flow wins. | | theme | ThemeConfig | Passed to the preview pane, merged over flow.config.theme — the prop wins. | | navigation | NavigationConfig | Passed to the preview pane. | | controls | ControlsConfig | Passed to the preview pane. | | options | FBTLOptions | Builder-UI configuration that never touches the flow — see below. | | saveState | "idle" \| "saving" \| "saved" \| "error" | Drives the save pill. | | onSaveRetry | () => void | Retry button handler when saveState === "error". | | ctaPosition | "top" \| "bottom" | Where the Add-question / Add-message CTA row renders relative to the stage column. Default "bottom". | | stickyCta | boolean | When true (default), the CTA row stays pinned to its edge of the stage column while cards scroll. Set to false to inline the row inside the scroll area (legacy behavior). | | locale | string | i18n locale (future). | | className | string | Applied to the root container. |

options

| Key | Type | Description | |---|---|---| | screenModel | "paged" \| "single" \| "auto" | Whether the stage offers page breaks and the flow emits multiple screens. Default "auto", which picks "single" only for a form-family-styled flow already on one screen — a multi-screen flow is never collapsed unless the host passes "single" outright. Switching the value rewrites the break flags. | | allowedQuestionTypes | QuestionType[] | Narrows the add-question palette. Omit for every tile. | | lockedQuestionTypes | Partial<Record<QuestionType, string>> | Renders a tile disabled with the given reason, e.g. { date: "Available on the Pro plan" }. Shown even when allowedQuestionTypes leaves it out — the point of a lock is to say the type exists. |

QuestionType is the palette's own vocabulary — authoring tiles, not FBRE component types ("pickOne" covers yesNo / cardSelect / dropDown / radio). QUESTION_TYPES is exported for building these lists.

Note that nothing in flow.config implies a screen model: theme.style is presentational and the advance flags are behavioural, and screens stay screens either way. A host offering "one question at a time / all on one page" sets both the presentation (style + advance flags) and screenModel.

Composable layout

For custom layouts, use sub-components directly instead of <FBTL>:

import {
  FBTLProvider,
  FBTLDndZone,
  FBTLStage,
  FBTLStageCtaRow,
  FBTLPreview,
  FBTLWizardHost,
  FBTLUndoToast,
} from "@sonata-innovations/fiber-fbtl";

<FBTLProvider flow={flow} onChange={onChange}>
  <div className="fbtl-container my-layout">
    <FBTLDndZone>
      {/* FBTLStage renders the Add-question/Add-message row inline by
          default for back-compat. Pass inlineCta={false} and render
          <FBTLStageCtaRow /> as a sibling to pin it outside the scroll
          area (matches what top-level <FBTL> does). */}
      <FBTLStage />
    </FBTLDndZone>
    <FBTLPreview />
    <FBTLWizardHost />
    <FBTLUndoToast />
  </div>
</FBTLProvider>

Exports

// Top-level component
import { FBTL } from "@sonata-innovations/fiber-fbtl";

// Composable sub-components
import {
  FBTLProvider,
  FBTLDndZone,
  FBTLStage,
  FBTLStageCtaRow,
  FBTLPreview,
  FBTLWizardHost,
  FBTLUndoToast,
  FBTLSaveStatePill,
  FBTLInvalidFlowBanner,
} from "@sonata-innovations/fiber-fbtl";

// Store hooks
import { useFBTLStore, useFBTLStoreApi } from "@sonata-innovations/fiber-fbtl";

// Flow <-> flat-card helpers
import { flattenFlow, unflattenFlow, componentsToScreens } from "@sonata-innovations/fiber-fbtl";

// Types
import type { FBTLProps, SaveState, FBTLStoreState } from "@sonata-innovations/fiber-fbtl";

Controlled-component contract

FBTL owns no flow state of its own. On every edit it serializes a new Flow object and hands it to onChange; the parent stores it and feeds it back in via the flow prop. This is the same contract as a controlled <input value onChange>.

The hazard to know about: if your state layer hands FBTL back an object that is equal in content but different by reference — a reducer that clones, a normalizer, a deep-copy, setFlow({ ...next }), a React Query cache, or a form library — you create a feedback loop candidate:

edit → onChange(newFlow) → parent re-creates the object → flow prop changes
     → FBTL re-hydrates → emits again → parent re-creates again → …
  • fbtl ≥ 2.1.2 closes this: a hydrate whose content matches what the store would currently emit is a no-op, so a re-created-but-equal flow can no longer drive an unbounded loop. The simple useState pattern was always safe; this hardens the normalizing-parent case too.
  • fbtl ≤ 2.1.1 only short-circuits when you pass back the exact same object reference it gave you. With a normalizing parent on those versions you'll see "Maximum update depth exceeded." Fix by upgrading, or by preserving the object identity FBTL handed you when the flow hasn't actually changed.

This is purely an FBTL ↔ parent concern. It is not a fiber-fbre issue, not a React 19 issue (FBTL is built and tested on React 18 and 19), and not caused by the drag-and-drop component or a Zustand selector — the internal components selector is reference-stable.

Styling

All visual values are CSS custom properties. They ship bundled in the published stylesheet (dist/fiber-fbtl.css, the ./styles export you import) — the token block sits at the top of that file. To read the full token list, open node_modules/@sonata-innovations/fiber-fbtl/dist/fiber-fbtl.css.

Override any token at a selector above the FBTL root:

.my-app {
  --fbtl-primary: #4f46e5;
  --fbtl-primary-dark: #4338ca;
  --fbtl-radius-md: 8px;
}

Component styles use only token variables — no hardcoded colors or pixel values — so overriding the tokens restyles the whole widget.

FBTL's preview pane and add-question wizard mount a real <FBRE>, whose stylesheet ships separately from its JS. Since 3.0.1 it is bundled into dist/fiber-fbtl.css, so the single ./styles import above covers the preview too. On ≤ 3.0.0 it was not, and the preview rendered as unstyled text under a correctly styled builder — the fix on those versions is to also import "@sonata-innovations/fiber-fbre/styles". If you render <FBRE> yourself to publish the finished form, that instance still needs its own stylesheet import.

Component support

FBTL produces and consumes the full Flow JSON schema, but its authoring surface is intentionally smaller than the FBRE component registry. The matrix below is the authoritative answer to "can my end users build/edit this, or does it need to be pre-authored in FBT?" — useful when deciding what to preload into a flow.

Legend:

  • Create — the end user can add this via the FBTL creation wizard.
  • Edit — if present in a loaded flow, what the stage card exposes.
  • Round-trip — survives load → emit unchanged (✓ for every registered FBRE component).

| FBRE component | Create | Edit surface | Round-trip | |---|---|---|---| | inputText | ✓ Short answer | label, required, format chip (read-only) | ✓ | | inputNumber | ✓ via Short answer → Number format | label, required, format chip (read-only) | ✓ | | inputTextArea | ✓ Long answer | label, required | ✓ | | dropDown, radio, cardSelect | ✓ Pick one | label, required, display switcher, options editor* | ✓ | | dropDownMulti, checkbox | ✓ Pick several | label, required, display switcher, options editor* | ✓ | | yesNo | ✓ (auto when Pick one has ≤2 options) | label, required, display switcher | ✓ | | confirm | ✓ Confirmation | label, required | ✓ | | date, dateTime | ✓ Date (the "Show as" sub-choice picks which) | label, required, allowed dates, date format, allowed times + increments (dateTime) | ✓ | | time | ✓ Time | label, required, allowed times, increments | ✓ | | header + text (wrapped in a group) | ✓ Information Screen | heading, paragraph | ✓ | | dateRange, timeRange, dateTimeRange | — | label, required only | ✓ | | slider, rating, fileUpload, signature, colorPicker, toggleSwitch | — | label, required only | ✓ | | callout, divider, table | — | label, required (Required is usually nonsensical here) | ✓ | | computed | — | label, required + advanced badge (calculation is read-only) | ✓ | | repeater | — | label, required + advanced badge (inner components are read-only) | ✓ | | group (header/text only) | ✓ as Information Screen | heading, paragraph | ✓ | | group (mixed content) | — | — (renders as a read-only "Section" card) | ✓ |

* Options editor is disabled on components whose options carry metadata — those are treated as rich/advanced and get a badge instead.

Advanced-property preservation. When a loaded component has features FBTL can't edit — multi-rule conditions, cross-field validation, complex validators, reference markup ({{…}}), rich option metadata, calculations, type-specific settings with no FBTL editor (a range's bounds, a slider's step), or is a repeater / computed — it renders with a "Has custom configuration" badge. Click the badge to see exactly which of those apply. The properties are round-tripped verbatim; FBTL is self-contained and does not offer an "Edit in FBT" link.

Guidance for integrators. If your end users need to author a given question type, prefer seeding flows with components from the ✓ Create rows above. Components from the "Edit" column work but only expose label/required, so pre-configure their properties (accept lists, range bounds, etc.) in FBT or at the data layer before handing the flow to FBTL.

Other v1 scope

Creation-side features beyond the component matrix:

  • Required toggle on every question
  • Short Answer format sub-choice (Any / Email / Phone / URL / Number)
  • Single-rule conditions between top-level questions (drag-to-condition or Make Conditional button)
  • Per-card page-break toggles (v1.2): the small pill between adjacent cards toggles whether the next card lands on its own screen or shares the screen above. New questions default to ending a screen, preserving the historical one-question-per-screen feel until the author opts in. A host that wants the form on a single page throughout sets options.screenModel: "single", which hides the toggles and emits one screen.
  • Date / Time question settings: allowed dates (including a relative "today or later" bound), date format, allowed times and increments — editable at creation and afterwards, on any date component in the flow, including one the builder did not create.
  • A note on every conditional band saying which behaviour the rule produces: a skipped screen, or a question hidden in place on a screen the visitor still sees.

Not in FBTL v1:

  • Editable screen labels or a first-class screen container concept (screen boundaries are expressed only via the page-break toggles)
  • Custom component creation, calculations editor, reference markup editor
  • Full undo/redo (only a delete-toast undo is implemented)