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

@factorialco/rjsf-f0

v2.3.1

Published

Factorial f0 theme for react-jsonschema-form: renders RJSF forms with f0 components.

Readme

@factorialco/rjsf-f0

The f0 theme for react-jsonschema-form. It renders an RJSF form with real f0 components — inputs, selects, checkboxes, buttons — instead of styling plain HTML to look like f0.

Use it inside an app that already runs f0 (the Factorial Code dashboard, the Factorial monolith). Apps that embed a form on an arbitrary page should keep the Forms SDK's own theme, which needs no design system: f0 cannot be tree-shaken, so pulling it in costs megabytes.

Install

pnpm add @factorialco/rjsf-f0

Peers you must already have: react, react-dom, @rjsf/core 6, @rjsf/utils 6 and @factorialco/f0-react 4.39+ or 6.

Usage

With the Factorial Code Forms SDK — it keeps fetching the form spec and submitting, and only the rendering changes:

import FcodeForm from '@factorialco/fcode-react-forms';
import { Theme, markdownComponents } from '@factorialco/rjsf-f0';
import '@factorialco/rjsf-f0/styles.css';

<FcodeForm
  team="my-team"
  processId="my-process"
  rjsfTheme={Theme}
  // Renders the execution result message (markdown) with f0 components —
  // GFM tables become the f0 table card with Excel/CSV export.
  markdownComponents={markdownComponents}
  // Suppresses the SDK's standalone stylesheet, which resets inherited styles
  // and would fight the host app's own.
  options={{ theme: 'none', locale: 'en' }}
/>;

Standalone, as any RJSF theme:

import Form from '@factorialco/rjsf-f0';
import validator from '@rjsf/validator-ajv8';
import '@factorialco/rjsf-f0/styles.css';

<Form schema={schema} validator={validator} />;

Also exported: Theme, Templates, Widgets, generateTheme(), generateForm() — matching the layout of the official @rjsf/* theme packages — and markdownComponents, f0's markdown tag renderers as a react-markdown components map (used internally for schema.markdown.before/after; pass it to FcodeForm for the result message).

The host app must render f0's F0Provider above the form and import @factorialco/f0-react/dist/styles.css; this package assumes both and only ships the layout rules for the parts RJSF composes. The F0Provider also serves the markdown table card, whose title and export menu resolve through f0's i18n; exporting a table lazily imports xlsx through f0's own dependencies.

How the mapping works

| RJSF | f0 | | --- | --- | | text / email / url / password | F0FormField text | | number / integer / updown / range | F0FormField number | | date / datetime-local / time | F0FormField date / datetime / time | | textarea | F0FormField textarea | | enum select, multi-select | F0FormField select | | boolean checkbox, checkboxes | F0Checkbox | | radio | F0FormField cardSelect (f0 has no radio control) | | file / data-url | f0-styled dropzone + file chips (see below) | | submit, add/move/copy/remove | F0Button | | oauth connect control | F0Button (see below) | | error summary | F0Alert | | section title | F0Heading |

Two deliberate consequences of that mapping:

  • RJSF keeps ownership of validation. f0 fields normally validate with zod; here they are told they are in an error state (error) rather than deriving one, so the schema stays the single source of truth. The messages are rendered by the theme's FieldErrorTemplate, not by the control: half of this mapping is not an F0FormField at all — checkboxes, the file dropzone, and the object and array levels a global form error lands on — so a control-rendered message would be dropped for exactly those.
  • Labels are rendered by the theme, not by f0. Factorial Code form labels may contain markdown, which f0 takes as a plain string. The theme renders the markdown label itself and hands f0 the plain text so the control keeps an accessible name.

File fields

FileWidget replaces core's, which renders a bare <input type="file">. That input gives away a non-native dialog in two ways CSS cannot reach: its "No file chosen" text, and a browse button the browser labels in the browser's locale — English chrome inside a Spanish form, which no locale propagation on our side can fix.

The theme draws its own dropzone and one chip per picked file (name, human-readable size, remove button). The value is unchanged: core's own useFileWidgetProps still owns the File -> data: URL exchange, so what RJSF stores is byte-identical.

It does not bridge to f0's F0FormField file. That field is driven by an upload hook returning an opaque id, and — the blocker — its accept is a closed MimeType allowlist with no file extensions. ui:options.accept is a raw HTML accept string, so a schema asking for .msj (or any extension without a registered MIME type) would silently lose its filter. The dropzone keeps the string verbatim.

ui:options:

| Option | Effect | | --- | --- | | accept | Passed to the input verbatim. Also shown as a hint under the dropzone text. | | dropzoneText | Replaces the dropzone copy, before any translation. For a marketplace app this is the path that works: schema strings go through the platform's i18n token substitution and need no cooperation from the embedder. |

Localising the chrome

Every string this theme renders that the schema did not author goes through RJSF's translateString — Add Item, Remove, Move up, Move down, Copy, Errors, and the dropzone copy. All of them are English until a host supplies a translator, because RJSF's default translator is the identity function and its TranslatableString values are the English text (RemoveButton is the literal 'Remove'). Nothing in this package translates on its own.

One prop on the form localises the lot:

<FcodeForm
  translateString={(key, params) => myTable[key] ?? englishStringTranslator(key, params)}
  …
/>

The dropzone's two defaults are keyed on their English text, since RJSF has no enum member for them:

  • Drag a file here, or click to choose one
  • Drag files here, or click to choose them (when multiple)

A form rendered inside a localised product without this prop shows English buttons next to translated field labels. ui:options.dropzoneText fixes only the dropzone; the translator fixes everything.

Markdown alerts

markdownComponents renders a blockquote opening with a GitHub alert marker as an f0-toned callout:

> [!NOTE]
> **Nothing is written yet.** You will see a preview first.

| Marker | f0 variant | | --- | --- | | [!NOTE] | info | | [!TIP] | positive | | [!IMPORTANT] | info | | [!WARNING] | warning | | [!CAUTION] | critical |

Any other blockquote still renders as f0's blockquote, and an unrecognised marker is left as the text it is.

Two things worth knowing:

  • These are not GFM. The GFM spec covers tables, task lists, strikethrough, autolinks and footnotes; alerts are a GitHub.com convention and remark-gfm does not parse them. The marker survives parsing as ordinary text at the start of the quote, which is what this renderer keys off — so no remark plugin and no new dependency is involved, and the shared markdown-content.tsx (and with it the hosted bundle) is untouched. It also means that without this renderer a literal [!NOTE] is printed to the user.
  • The callout is not F0Alert. That component's title and description are plain strings, so routing a blockquote through it would flatten the body and drop the bold, links, lists and code an author may legitimately put in a markdown block. The callout keeps the real markdown children and borrows f0's semantic colour scale, so it still flips in dark mode.

Markdown heading levels

A schema author choosing # or ### inside markdown.before / markdown.after (or a result message) cannot see what the form's own title renders at. This is that scale, measured, so the choice can be made deliberately.

f0's type scale is not Tailwind's default — f0/packages/core/src/tokens/typography.ts puts base at 14px, lg at 16px and 2xl at 22px. So:

| | Source | Renders at | | --- | --- | --- | | The form's own title (schema title) | F0Heading variant="heading" | 16px / 600 | | # | f0 text-2xl font-medium | 22px / 500 | | ## | f0 text-lg font-medium | 16px / 500 | | ### | f0 text-base font-semibold | 14px / 600 | | #### | this package's stylesheet | 14px / 600 | | ##### | this package's stylesheet | 14px / 500 | | ###### | this package's stylesheet | 14px / 500, secondary | | body text | f0 text-base font-normal | 14px / 400 | | **bold** | f0 font-semibold | 14px / 600 |

There is no rule relating these to the form's title. Read the table before picking a level, because three of the rows collide:

  • # outranks the form's title. 22px against the title's 16px, so a heading inside the form is larger than the form it sits in. Avoid # in a form.
  • ### is identical to **bold** — both 14px/600. A ### section heading is indistinguishable from a bold sentence. #### lands on the same values, so it is indistinguishable from both.
  • ## matches the title's size at one weight step lighter, which reads as a peer of the title rather than a section under it.

The compression is f0's, not this package's: between the title (16/600) and body text (14/400) the scale has two slots, and markdown has six levels — so any mapping has to collapse levels rather than preserve them.

Which level reads as a section depends on what sits directly below it, which is the part the table cannot tell you. A markdown.before heading is almost always followed by one of two things, and they are not the same weight:

| Directly below | Renders at | So the heading above it | | --- | --- | --- | | A plain field's label | 16px / 500, dimmed | ### (14/600, full strength) reads clearly as a heading — smaller, but bolder and darker | | An object's or array's title (TitleFieldTemplate → F0Heading) | 16px / 600, full strength | ### reads as subordinate to the field it introduces; nothing available outranks it, since ## is 16/500 and # would outrank the form itself |

So ### is the right default above an ordinary field and the wrong one above an array — and ## is not a fix for the second case, because it only matches the title's size while staying a weight lighter. dev/main.tsx renders both levels above a field so the difference can be seen rather than reasoned about.

A heading above an object or array is usually the wrong shape rather than the wrong level. Those fields already render their own title at 16/600, so a markdown heading above one is a second title for the same thing — which is what makes it read badly no matter which level it uses. Two ways out, both better than picking a different #:

---

A rule opens the group without competing for the title: it renders as a 1px hairline with 12px of clearance, and the field's own title becomes the section heading. Use this when the point of the heading was to separate the group from what came before it.

Or drop the markdown block and let the title carry the section on its own. Use this when the heading and the title were saying the same thing.

A heading earns its place when it opens a group of several fields, each with its own label — there it names something no single field's title can.

Arrays of scalars

A list of plain values — addresses, tags, ids — renders as one row per item: the control, then the item toolbar, then the array's add button.

Rows carry no label of their own. RJSF titles each one after the array plus its position (Emails-1, Emails-2) and marks every non-null item required, which on an optional list of two addresses produced a repeated heading and two asterisks. The array's own title already names the group, and the control keeps its accessible name through f0's hideLabel. Rows holding an object or a nested array are unaffected: those are blocks of several fields, and their labels are what separate one from the next.

The toolbar is RJSF's, so it is the schema that decides what a row can do:

{ "ui:options": { "orderable": false } }

orderable (default true) draws move up/down — turn it off when order carries no meaning, which is most scalar lists. copyable defaults to false already, removable and addable to true.

OAuth connect fields

The Forms SDK's "ui:widget": "oauth" field opens the provider in a popup and reacts when the flow finishes; the widget logic ships in @factorialco/fcode-react-forms. This theme only contributes the control it draws, as Theme.oauth.ConnectControl: an F0Button (default while idle or waiting, outline with a check icon once connected) plus the flow's error message underneath. FcodeJsonSchemaForm finds it through rjsfTheme.oauth, the same way it finds rjsfTheme.multistep.

Not yet f0

  • range renders as a number input rather than F0Slider.
  • Headings h1–h3 come from f0's own renderers; h4–h6 are styled by this package's stylesheet instead, since a renderer here would have to carry Tailwind utility classes that the host's Tailwind build never generates (its content globs do not include this package).
  • The Forms SDK's sourceCode field keeps its Monaco editor, which is not f0-styled.

Development

pnpm test      # vitest: renders forms through the theme and checks value round-trips
pnpm dev       # vite playground with every widget
pnpm build     # tsc + vitest + vite (es + cjs + types + styles.css)