@factorialco/rjsf-f0

中文文档
2.6.0发布于 2026/11/09See LICENSE fileTypeScriptESM + CJS近 30 天下载 16.3万

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

原始使用文档
文档目录11 节
  1. Install
  2. Usage
  3. How the mapping works
  4. File fields
  5. Localising the chrome
  6. Markdown alerts
  7. Markdown heading levels
  8. Arrays of scalars
  9. OAuth connect fields
  10. Not yet f0
  11. Development

@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

RJSFf0
text / email / url / passwordF0FormField text
number / integer / updown / rangeF0FormField number
date / datetime-local / timeF0FormField date / datetime / time
textareaF0FormField textarea
enum select, multi-selectF0FormField select
boolean checkbox, checkboxesF0Checkbox
radioF0FormField cardSelect (f0 has no radio control)
file / data-urlf0-styled dropzone + file chips (see below)
submit, add/move/copy/removeF0Button
oauth connect controlF0Button (see below)
error summaryF0Alert
section titleF0Heading

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.

One exception, opt-in: when the form provides formContext.fcode.keptFiles (FcodeForm from @factorialco/fcode-react-forms does), a single file larger than 50 MiB (LARGE_FILE_BYTES) in a root field is not read at all. Base64 of a file near 1 GB is longer than the longest string a browser can hold, so encoding it fails before the form is submitted; a file that size can only travel through an upload grant anyway, which sends the File as it is. The widget hands the File to keptFiles.keep(file) and stores the string it returns: a data: URL with no content and a fcode-kept parameter, so validation, conditional schemas and JSON copies of the form data treat it like any other file value. keptFiles.get(value) returns the File behind it, and the host swaps it back in at submit. Multiple-file and nested fields always encode. A host rendering this theme on its own provides no keptFiles and keeps getting data: URLs with their content. If the browser cannot read a picked file, the widget shows an error and keeps the previous value.

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:

OptionEffect
acceptPassed to the input verbatim. Also shown as a hint under the dropzone text.
dropzoneTextReplaces 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.
Markerf0 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:

SourceRenders at
The form's own title (schema title)F0Heading variant="heading"16px / 600
#f0 text-2xl font-medium22px / 500
##f0 text-lg font-medium16px / 500
###f0 text-base font-semibold14px / 600
####this package's stylesheet14px / 600
#####this package's stylesheet14px / 500
######this package's stylesheet14px / 500, secondary
body textf0 text-base font-normal14px / 400
**bold**f0 font-semibold14px / 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 belowRenders atSo the heading above it
A plain field's label16px / 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)

版本与发布记录

@factorialco/rjsf-f0 共发布 13 个版本,首次发布于 2026/08/07;过去 12 个月发布了 13 个版本。

版本发布日期距今
2.6.0latest2026/11/095 天前
2.5.02026/11/0311 天前
2.4.02026/10/3114 天前
2.3.12026/09/251 个月前
2.3.02026/09/072 个月前
2.2.12026/08/282 个月前

关于 @factorialco/rjsf-f0 的常见问题

@factorialco/rjsf-f0 是什么?

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

如何安装 @factorialco/rjsf-f0?

在项目目录运行 npm install @factorialco/rjsf-f0,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.3.1 || ^19.0.0)、react-dom(^18.3.1 || ^19.0.0)、@rjsf/core(^6.7.1)、@rjsf/utils(^6.7.1)、@factorialco/f0-react(^4.39.0 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@factorialco/rjsf-f0 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@factorialco/rjsf-f0 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@factorialco/rjsf-f0 可以商用吗?

@factorialco/rjsf-f0 使用 See LICENSE file 许可。声明的许可为 See LICENSE file,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

@factorialco/rjsf-f0 的下载量有多少?

根据 npm 官方下载统计,@factorialco/rjsf-f0 在 2026/10/14 – 2026/11/12 这 30 天内被下载 162,582 次,日均约 5,419 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@factorialco/rjsf-f0 还在维护吗?

最新版本 2.6.0 发布于 2026/11/09(5 天前);至今共发布 13 个版本,其中过去 12 个月发布了 13 个;首次发布于 2026/08/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。