@sonata-innovations/fiber-types 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,428 @@
1
+ ---
2
+ title: Custom Presets & Templates (FBT)
3
+ applies-to:
4
+ - "@sonata-innovations/fiber-fbt@^3.0"
5
+ - "@sonata-innovations/fiber-types@^3.0"
6
+ read-when: "Extending FBT's pool with custom presets/templates: data-based vs factory definitions, icon catalog, collision rules, server-stored definitions."
7
+ ---
8
+
9
+ <!-- Generated from the Fiber repo's docs/ tree by project/scripts/sync-package-docs.mjs. Do not edit here. -->
10
+ # Custom Presets & Templates
11
+
12
+ Parent applications extend FBT's pool with custom **presets** and **templates** via the optional `customPresets` and `customTemplates` props. This is the deep-dive companion to the short "Custom Presets & Templates" section in `docs/integration/fbt.md`.
13
+
14
+ ## Concepts
15
+
16
+ - A **preset** is one or more pre-configured components. Dropping it onto the stage batch-adds those components to the **current screen** (at the drop position if dropped over an existing component, otherwise appended).
17
+ - A **template** is a complete `Flow`. Dropping it **merges** into the current flow: its screens are **appended** to the existing screen list via `mergeFlow`, and the first appended screen becomes the active screen. Nothing is replaced — the existing screens, metadata, and config are untouched, and the template's own `metadata`/`config` are ignored by the merge (only its screens are ingested).
18
+
19
+ Whether a definition is treated as a preset or a template is determined by **which prop array it arrives in** (`customPresets` vs `customTemplates`), not by its `type` string.
20
+
21
+ ## Two definition kinds, one set of props
22
+
23
+ Both props accept a union type:
24
+
25
+ ```ts
26
+ // fbt/src/types/custom.ts
27
+ export type AnyPresetDefinition = CustomPresetDefinition | PresetData;
28
+ export type AnyTemplateDefinition = CustomTemplateDefinition | TemplateData;
29
+ ```
30
+
31
+ - **Data-based** (`PresetData` / `TemplateData`, exported from `@sonata-innovations/fiber-types`) — plain serializable JSON. **Preferred**: it can be stored in a database, sent over the wire, and edited without code. FBT regenerates UUIDs automatically on every drop.
32
+ - **Factory-based** (`CustomPresetDefinition` / `CustomTemplateDefinition`, exported from `@sonata-innovations/fiber-fbt`) — a `factory()` function called on every drop. Kept for backward compatibility.
33
+
34
+ FBT distinguishes the two kinds structurally (`"factory" in definition`), so you can mix both kinds in the same array.
35
+
36
+ ## Data-based definitions (primary)
37
+
38
+ ### Shapes
39
+
40
+ ```ts
41
+ // @sonata-innovations/fiber-types
42
+ export type PresetData = {
43
+ type: string; // unique identifier (see collision rules below)
44
+ label: string; // display name in the pool
45
+ icon: string; // key into FBT's ICON_MAP (see icon catalog below)
46
+ components: Component[];
47
+ };
48
+
49
+ export type TemplateData = {
50
+ type: string;
51
+ label: string;
52
+ icon: string;
53
+ flow: Flow;
54
+ };
55
+ ```
56
+
57
+ ### UUID regeneration semantics
58
+
59
+ The UUIDs you write into a data-based definition are **placeholders**. On every drop, FBT calls `regenerateComponentUUIDs` (presets) or `regenerateFlowUUIDs` (templates) from `@sonata-innovations/fiber-types`:
60
+
61
+ - Every component gets a fresh UUID; `properties` are deep-cloned (`structuredClone`), and nested `components` arrays (groups) are regenerated recursively.
62
+ - For templates, the flow UUID and every screen UUID are also replaced.
63
+ - The original definition object is never mutated, so the same definition can be dropped any number of times without duplicate-UUID bugs.
64
+
65
+ This means placeholder UUIDs only need to be unique **within** the definition (so group nesting is unambiguous); any string works.
66
+
67
+ ### Full preset example
68
+
69
+ `Component` is a discriminated union keyed on `type` — `properties` is checked against the shape for that `type` (e.g. `label` is required for `"inputText"`, `value` for `"header"`, `options` for `"dropDown"`). Group children live on the component's top-level `components` field, not inside `properties`.
70
+
71
+ ```ts
72
+ import type { PresetData } from "@sonata-innovations/fiber-types";
73
+
74
+ const ssnPreset: PresetData = {
75
+ type: "custom-ssn",
76
+ label: "SSN",
77
+ icon: "ssn",
78
+ components: [
79
+ {
80
+ uuid: "ssn-field",
81
+ type: "inputText",
82
+ properties: {
83
+ label: "Social Security Number",
84
+ placeholder: "XXX-XX-XXXX",
85
+ validation: {
86
+ rules: [
87
+ { type: "required" },
88
+ // NOTE: the param key is `regex`, not `pattern`
89
+ { type: "pattern", params: { regex: "^\\d{3}-\\d{2}-\\d{4}$" } },
90
+ ],
91
+ },
92
+ },
93
+ },
94
+ ],
95
+ };
96
+
97
+ const emergencyContactPreset: PresetData = {
98
+ type: "custom-emergencyContact",
99
+ label: "Emergency Contact",
100
+ icon: "emergencyContact",
101
+ components: [
102
+ {
103
+ uuid: "ec-group",
104
+ type: "group",
105
+ properties: { label: "Emergency Contact", showLabel: true },
106
+ components: [
107
+ {
108
+ uuid: "ec-name",
109
+ type: "inputText",
110
+ properties: {
111
+ label: "Contact Name",
112
+ width: "half",
113
+ validation: { rules: [{ type: "required" }] },
114
+ },
115
+ },
116
+ {
117
+ uuid: "ec-phone",
118
+ type: "inputText",
119
+ properties: {
120
+ label: "Contact Phone",
121
+ placeholder: "(555) 555-5555",
122
+ inputType: "tel",
123
+ width: "half",
124
+ validation: { rules: [{ type: "required" }, { type: "phone" }] },
125
+ },
126
+ },
127
+ {
128
+ uuid: "ec-relationship",
129
+ type: "dropDown",
130
+ properties: {
131
+ label: "Relationship",
132
+ options: [
133
+ { label: "Spouse", value: "spouse" },
134
+ { label: "Parent", value: "parent" },
135
+ { label: "Sibling", value: "sibling" },
136
+ { label: "Friend", value: "friend" },
137
+ { label: "Other", value: "other" },
138
+ ],
139
+ validation: { rules: [{ type: "required" }] },
140
+ },
141
+ },
142
+ ],
143
+ },
144
+ ],
145
+ };
146
+ ```
147
+
148
+ ### Full template example
149
+
150
+ ```ts
151
+ import type { TemplateData } from "@sonata-innovations/fiber-types";
152
+
153
+ const onboardingTemplate: TemplateData = {
154
+ type: "custom-employeeOnboarding",
155
+ label: "Employee Onboarding",
156
+ icon: "employeeOnboarding",
157
+ flow: {
158
+ uuid: "onboarding-flow",
159
+ metadata: { name: "Employee Onboarding" },
160
+ config: {},
161
+ screens: [
162
+ {
163
+ uuid: "onboarding-screen-1",
164
+ label: "Personal Info",
165
+ components: [
166
+ {
167
+ uuid: "ob-header-1",
168
+ type: "header",
169
+ properties: { value: "Employee Information" },
170
+ },
171
+ {
172
+ uuid: "ob-first-name",
173
+ type: "inputText",
174
+ properties: {
175
+ label: "First Name",
176
+ width: "half",
177
+ validation: { rules: [{ type: "required" }] },
178
+ },
179
+ },
180
+ {
181
+ uuid: "ob-last-name",
182
+ type: "inputText",
183
+ properties: {
184
+ label: "Last Name",
185
+ width: "half",
186
+ validation: { rules: [{ type: "required" }] },
187
+ },
188
+ },
189
+ {
190
+ uuid: "ob-email",
191
+ type: "inputText",
192
+ properties: {
193
+ label: "Email Address",
194
+ placeholder: "name@company.com",
195
+ inputType: "email",
196
+ validation: { rules: [{ type: "required" }, { type: "email" }] },
197
+ },
198
+ },
199
+ ],
200
+ },
201
+ {
202
+ uuid: "onboarding-screen-2",
203
+ label: "Employment Details",
204
+ components: [
205
+ {
206
+ uuid: "ob-header-2",
207
+ type: "header",
208
+ properties: { value: "Employment Details" },
209
+ },
210
+ {
211
+ uuid: "ob-job-title",
212
+ type: "inputText",
213
+ properties: {
214
+ label: "Job Title",
215
+ validation: { rules: [{ type: "required" }] },
216
+ },
217
+ },
218
+ {
219
+ uuid: "ob-employment-type",
220
+ type: "dropDown",
221
+ properties: {
222
+ label: "Employment Type",
223
+ options: [
224
+ { label: "Full-Time", value: "full-time" },
225
+ { label: "Part-Time", value: "part-time" },
226
+ { label: "Contract", value: "contract" },
227
+ { label: "Intern", value: "intern" },
228
+ ],
229
+ validation: { rules: [{ type: "required" }] },
230
+ },
231
+ },
232
+ ],
233
+ },
234
+ ],
235
+ },
236
+ };
237
+ ```
238
+
239
+ Remember: dropping this template does **not** overwrite the builder's current flow — its two screens are appended after the existing ones, and "Personal Info" becomes the active screen. The `metadata.name` above documents the template but is not merged into the current flow.
240
+
241
+ ### Passing to FBT
242
+
243
+ ```tsx
244
+ import { FBT } from "@sonata-innovations/fiber-fbt";
245
+ import "@sonata-innovations/fiber-fbt/styles";
246
+ import type { AnyPresetDefinition, AnyTemplateDefinition } from "@sonata-innovations/fiber-fbt";
247
+
248
+ const MY_PRESETS: AnyPresetDefinition[] = [ssnPreset, emergencyContactPreset];
249
+ const MY_TEMPLATES: AnyTemplateDefinition[] = [onboardingTemplate];
250
+
251
+ function App() {
252
+ return (
253
+ <FBT
254
+ flow={existingFlow}
255
+ onFlowChange={handleFlowChange}
256
+ customPresets={MY_PRESETS}
257
+ customTemplates={MY_TEMPLATES}
258
+ />
259
+ );
260
+ }
261
+ ```
262
+
263
+ Both props are optional; if omitted, FBT shows only its built-in items. Keep the arrays referentially stable (module constants or memoized) — the context memoizes its lookup maps on the array identities.
264
+
265
+ ## Factory-based definitions (back-compat)
266
+
267
+ ```ts
268
+ // @sonata-innovations/fiber-fbt
269
+ export type CustomPresetDefinition = {
270
+ type: string;
271
+ label: string;
272
+ icon: string;
273
+ factory: () => Component[];
274
+ };
275
+
276
+ export type CustomTemplateDefinition = {
277
+ type: string;
278
+ label: string;
279
+ icon: string;
280
+ factory: () => Flow;
281
+ };
282
+ ```
283
+
284
+ With a factory, **you** are responsible for returning fresh UUIDs on every call — FBT does not regenerate UUIDs for factory-based definitions. You would still use a factory when the definition must be computed at drop time (e.g. injecting the current date, user data, or environment-dependent options):
285
+
286
+ ```ts
287
+ import type { CustomPresetDefinition } from "@sonata-innovations/fiber-fbt";
288
+
289
+ const visitDatePreset: CustomPresetDefinition = {
290
+ type: "custom-visitDate",
291
+ label: "Visit Date",
292
+ icon: "date",
293
+ factory: () => [
294
+ {
295
+ uuid: crypto.randomUUID(),
296
+ type: "date",
297
+ properties: {
298
+ label: "Visit Date",
299
+ min: new Date().toISOString().slice(0, 10), // today, computed at drop
300
+ validation: { rules: [{ type: "required" }] },
301
+ },
302
+ },
303
+ ],
304
+ };
305
+ ```
306
+
307
+ For anything static, prefer `PresetData`/`TemplateData`.
308
+
309
+ ## How resolution works
310
+
311
+ 1. **Context injection** — the FBT provider builds `presetMap` / `templateMap` (`Map<string, () => Component[] | Flow>`) from the prop arrays, keyed by `type`. Factory definitions map to their `factory`; data definitions map to a wrapper that regenerates UUIDs. If two custom definitions share a `type`, the later one in the array wins (plain `Map.set` overwrite).
312
+ 2. **Kind by prop array** — items from `customPresets` are tagged `kind: "preset"`, items from `customTemplates` are tagged `kind: "template"` when the pool renders them. The `type` string plays no part in this.
313
+ 3. **Built-in-first lookup at drop** — the DnD handler resolves the dropped `type` against the **built-in** definitions first, and only falls back to the custom map if that returns nothing:
314
+
315
+ ```ts
316
+ // fbt/src/ui/dnd/dnd-wrapper.tsx
317
+ const flow = createTemplate(item.type) ?? templateMap.get(item.type)?.();
318
+ const components = createPreset(item.type) ?? presetMap.get(item.type)?.();
319
+ ```
320
+
321
+ ### Collision rules
322
+
323
+ There is **no required prefix** on custom `type` strings — FBT accepts any string. But because built-in lookup runs first, a custom definition whose `type` equals a built-in key is **silently shadowed**: your pool entry shows your label and icon, but dropping it produces the built-in content.
324
+
325
+ Built-in keys to avoid — presets: `preset-phone`, `preset-email`, `preset-date`, `preset-url`, `preset-fullName`, `preset-dollarAmount`, `preset-password`, `preset-addressBlock`, `preset-contactInfo`, `preset-nameFields`, `preset-agreement`; templates: `template-contactForm`, `template-signUp`, `template-patientIntake`, `template-feedbackSurvey`.
326
+
327
+ **Recommendation:** use a distinct namespace such as `custom-*`. No built-in uses it, and it matches what the Fiber server enforces for stored definitions (see below).
328
+
329
+ ## Icon catalog
330
+
331
+ `icon` is a key into FBT's `ICON_MAP` (`fbt/src/lib/icons.tsx`) — 46 built-in SVG icons. Icon keys are their own namespace: they often mirror component types but are not the same set (e.g. the paragraph-text icon is `descriptionText` while the component type is `text`; `select` is the dropdown icon). If the key is unknown, the item renders with an empty icon slot — no error, graceful fallback.
332
+
333
+ ### Display & structure
334
+
335
+ | Key | Glyph |
336
+ |-----|-------|
337
+ | `header` | Heading lines |
338
+ | `descriptionText` | Paragraph lines |
339
+ | `divider` | Horizontal rule |
340
+ | `callout` | Info box |
341
+ | `table` | Grid |
342
+
343
+ ### Text & number inputs
344
+
345
+ | Key | Glyph |
346
+ |-----|-------|
347
+ | `textInput` | Single-line field |
348
+ | `textArea` | Multi-line field |
349
+ | `numberInput` | Field with `#` |
350
+ | `dollarAmount` | Field with `$` |
351
+ | `phone` | Phone handset/device |
352
+ | `email` | Envelope |
353
+ | `url` | Chain link |
354
+ | `password` | Padlock |
355
+
356
+ ### Selection
357
+
358
+ | Key | Glyph |
359
+ |-----|-------|
360
+ | `select` | Dropdown |
361
+ | `multiSelect` | Checked list |
362
+ | `checkbox` | Checked box |
363
+ | `radio` | Radio dot |
364
+ | `toggleSwitch` | Switch |
365
+ | `yesNo` | Check/cross pair |
366
+ | `confirm` | Checked box (shares the `checkbox` glyph) |
367
+ | `cardSelect` | Card grid |
368
+
369
+ ### Date & time
370
+
371
+ | Key | Glyph |
372
+ |-----|-------|
373
+ | `date` | Calendar |
374
+ | `time` | Clock |
375
+ | `dateTime` | Calendar + clock |
376
+ | `dateRange` | Two calendars |
377
+ | `timeRange` | Two clocks |
378
+ | `dateTimeRange` | Two calendar+clock pairs |
379
+
380
+ ### Interactive & special
381
+
382
+ | Key | Glyph |
383
+ |-----|-------|
384
+ | `fileUpload` | Upload arrow |
385
+ | `rating` | Star |
386
+ | `slider` | Slider track |
387
+ | `colorPicker` | Color wheel |
388
+ | `signature` | Signature stroke |
389
+ | `repeater` | Stacked dashed rows |
390
+ | `calculated` | `fx` in a box |
391
+
392
+ ### People & preset-flavored
393
+
394
+ | Key | Glyph |
395
+ |-----|-------|
396
+ | `fullName` | Person |
397
+ | `addressBlock` | Map pin |
398
+ | `contactInfo` | Contact card |
399
+ | `nameFields` | Paired fields |
400
+ | `agreement` | Document with check |
401
+ | `ssn` | Masked field (`***`) |
402
+ | `emergencyContact` | Person with plus |
403
+
404
+ ### Template-flavored
405
+
406
+ | Key | Glyph |
407
+ |-----|-------|
408
+ | `signUpForm` | Form with avatar |
409
+ | `patientIntake` | Bulleted form |
410
+ | `feedbackSurvey` | Form with star |
411
+ | `contactForm` | Form lines |
412
+ | `employeeOnboarding` | Document lines |
413
+
414
+ ## Server-stored definitions
415
+
416
+ The Fiber server (private package, not published) provides tenant-scoped storage for data-based definitions:
417
+
418
+ - CRUD endpoints at `/api/v1/presets` and `/api/v1/templates`, storing `PresetData` / `TemplateData` JSON per tenant.
419
+ - The server **rejects** any stored definition whose `type` does not start with `custom-` — this prefix guarantees no collision with built-in keys.
420
+ - The portal provides management pages for presets and templates, and its flow editor fetches the tenant's custom definitions on mount.
421
+
422
+ From FBT's perspective there is nothing special about server-stored definitions: the parent app fetches them and passes them through the same `customPresets` / `customTemplates` props as data-based definitions.
423
+
424
+ ## UI behavior
425
+
426
+ - When either prop array is non-empty, the corresponding pool tab (Presets or Templates) appends a **CUSTOM** section after the built-in sections, containing one item per definition.
427
+ - Custom items carry a **star badge** in place of the colored accent that built-in preset/template items get; search results label them with a "Custom" badge.
428
+ - Items are drag-and-drop onto the stage. Icon resolution is `ICON_MAP[item.icon]` guarded by `{Icon && <Icon />}` — an unknown key renders nothing.
@@ -0,0 +1,217 @@
1
+ ---
2
+ title: Style Families & Advance Behaviors
3
+ applies-to:
4
+ - "@sonata-innovations/fiber-fbre@^4.0"
5
+ - "@sonata-innovations/fiber-types@^3.0"
6
+ - "@sonata-innovations/fiber-fbt@^3.0"
7
+ - "@sonata-innovations/fiber-fbtl@^3.0"
8
+ - "@sonata-innovations/fiber-theme-editor@^2.0"
9
+ read-when: "Choosing a form style, understanding the focused presentation, or turning auto-advance / Enter-to-advance on and off. Also: migrating off config.mode."
10
+ ---
11
+
12
+ <!-- Generated from the Fiber repo's docs/ tree by project/scripts/sync-package-docs.mjs. Do not edit here. -->
13
+ # Style Families & Advance Behaviors
14
+
15
+ A Fiber form's presentation is decided by one setting — `config.theme.style` —
16
+ and its two advance behaviors by two more — `config.navigation.autoAdvance` and
17
+ `config.navigation.advanceOnEnter`. That is the whole model. There is no
18
+ presentation mode.
19
+
20
+ > **Migrating from an earlier version?** `config.mode` (`"standard"` /
21
+ > `"conversational"`) was removed. Jump to [Migrating off `config.mode`](#migrating-off-configmode).
22
+
23
+ ## The ten styles are one flat vocabulary
24
+
25
+ Any style is valid on any flow. Four of them share an extra presentation
26
+ treatment, and that shared half is called a **family**:
27
+
28
+ | Family | Styles |
29
+ | --- | --- |
30
+ | `"form"` | `clean`, `outlined`, `refined-clean`, `airy-clean`, `soft-outlined`, `defined-outlined` |
31
+ | `"focused"` | `centered-minimal`, `stacked-cards`, `soft-float`, `bold-statement` |
32
+
33
+ On top of whichever of the four you pick, the focused family adds:
34
+
35
+ | Treatment | What it does |
36
+ | --- | --- |
37
+ | **Vertical centering** | Content sits centered in a narrow column, falling back to top-aligned and scrollable when a screen is taller than the viewport |
38
+ | **Animated entry** | Components fade and scale in with staggered delays. Respects `prefers-reduced-motion` |
39
+ | **Larger tap targets** | Option items, Yes/No buttons, card-select cards and inputs are enlarged |
40
+ | **Bolder type** | Headers, labels, prompts and inputs step up in size |
41
+
42
+ Each of the four styles then applies its own look — underline inputs, filled
43
+ cards, pill options, heavy borders — exactly as each of the six form styles
44
+ does.
45
+
46
+ ### The family is derived, never authored
47
+
48
+ It does not appear in Flow JSON. FBRE computes it from `theme.style` and emits
49
+ it on the container as `data-style-family`, next to `data-style`:
50
+
51
+ ```html
52
+ <div class="fbre-container" data-style="soft-float" data-style-family="focused">
53
+ ```
54
+
55
+ That is what lets the ~32 shared rules live under one selector instead of a
56
+ repeated four-way list. If you write custom CSS against a focused form, target
57
+ `[data-style-family="focused"]` for anything that should apply to all four and
58
+ `[data-style="…"]` for one style.
59
+
60
+ `fiber-types` (and `fiber-fbre`, which re-exports it) publishes the derivation
61
+ so a builder can group its own style picker from one list:
62
+
63
+ ```ts
64
+ import { FOCUSED_STYLES, styleFamily } from "@sonata-innovations/fiber-types";
65
+
66
+ styleFamily("bold-statement"); // "focused"
67
+ styleFamily("clean"); // "form"
68
+ styleFamily(undefined); // "form" — the `clean` default
69
+
70
+ FOCUSED_STYLES.has("soft-float"); // true
71
+ ```
72
+
73
+ ### Why this matters for server-driven rendering
74
+
75
+ The session protocol sends `config.theme`. Because presentation is *derived
76
+ from* `theme.style`, a focused-styled flow renders identically whether it is
77
+ rendered locally, fetched remotely, or served one screen at a time by a
78
+ session — no extra field has to be carried, and none can be forgotten.
79
+
80
+ ## Advance behaviors
81
+
82
+ Auto-advance and Enter-to-advance are independent `navigation` flags. Neither is
83
+ tied to a style: a `clean` form with one question per screen advances on Enter,
84
+ and a `bold-statement` flow only auto-advances if it asks to.
85
+
86
+ | Flag | Default | Behavior |
87
+ | --- | --- | --- |
88
+ | `navigation.autoAdvance` | `false` | Advance to the next screen ~500ms after a single-select choice — `radio`, `yesNo`, `cardSelect`, `dropDown`. Multi-select (`checkbox`, `dropDownMulti`) never auto-advances |
89
+ | `navigation.advanceOnEnter` | `true` | Advance when Enter is pressed in an `inputText` or `inputNumber`. `inputTextArea` is excluded — Enter inserts a newline there |
90
+
91
+ ```jsonc
92
+ {
93
+ "config": {
94
+ "theme": { "style": "centered-minimal" },
95
+ "navigation": {
96
+ "transition": "scaleFade",
97
+ "autoAdvance": true,
98
+ "advanceOnEnter": true
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ The defaults are deliberately asymmetric. A form that moves without a click is
105
+ surprising, and shifting content and focus ~500ms after a selection carries a
106
+ real accessibility cost — so `autoAdvance` is opt-in. Enter-to-advance is an
107
+ ordinary form convention, so it is on.
108
+
109
+ ### Guards
110
+
111
+ Both behaviors share the same guards. None of them are configurable — they are
112
+ correctness guards, not policy:
113
+
114
+ - Neither fires on a screen with **more than one visible input**. A shared
115
+ screen behaves like a normal form, so components after the first are never
116
+ skipped. (Conditionally hidden inputs don't count toward the total.)
117
+ - Neither fires on the last screen — so Enter can never submit the form.
118
+ - Neither fires when the screen fails validation.
119
+ - Both respect condition-hidden screens, skipping to the next reachable one.
120
+ - Auto-advance does not fire during an active screen transition.
121
+ - Enter-to-advance stands down inside an open popup (`.fbre-popup`), so Enter
122
+ there commits the value rather than skipping the screen. Today this affects
123
+ one component: the colour picker's hex field is the only `<input>` rendered
124
+ inside an overlay. The date/time pickers and both dropdowns use buttons and
125
+ `role="option"` elements, which Enter-to-advance already ignores.
126
+
127
+ The single-visible-input guard is worth designing around: turning `autoAdvance`
128
+ on for a flow whose screens hold several questions each does nothing at all.
129
+ Both builders say so — FBT warns on a multi-input screen tab, FBTL notes it on
130
+ the divider that merges two cards onto one screen.
131
+
132
+ ## In the builders
133
+
134
+ Neither builder stores a mode. Each offers a preset that writes the settings the
135
+ focused look is made of, after which every one of them stays individually
136
+ editable:
137
+
138
+ - **FBT** — a *Focused presentation* preset in the Appearance section writes
139
+ `theme.style: "centered-minimal"` and `navigation: { transition: "scaleFade",
140
+ autoAdvance: true }`. The style picker lists all ten styles, captioned *Form*
141
+ and *Focused*.
142
+ - **FBTL** — its default config already uses `centered-minimal` with
143
+ `autoAdvance: true`. Pacing (one question per screen vs all on one page) is a
144
+ separate concern there: see `screenModel` in the
145
+ [FBTL Integration Guide](@sonata-innovations/fiber-fbtl/docs/integration/fbtl.md#screen-model).
146
+ - **Theme Editor** — the style dropdown lists all ten, grouped under *Form* and
147
+ *Focused* headings. Its value is `{ theme }`.
148
+
149
+ ## Migrating off `config.mode`
150
+
151
+ `FlowConfiguration.mode`, the `FlowModeType` type, and FBRE's `mode` prop are
152
+ gone. Each of `mode`'s jobs moved somewhere it belongs:
153
+
154
+ | Was | Now |
155
+ | --- | --- |
156
+ | `mode: "conversational"` for the centered, animated presentation | the focused **style** already on the flow — nothing to set |
157
+ | `mode: "conversational"` for auto-advance | `navigation.autoAdvance: true` |
158
+ | `mode: "conversational"` for Enter-to-advance | `navigation.advanceOnEnter` — on by default now, for every flow |
159
+ | `mode` deciding which styles were legal | nothing — the vocabulary is flat |
160
+
161
+ ### Updating a stored flow
162
+
163
+ A flow that had `mode: "conversational"` already carried a focused
164
+ `theme.style`, so **its look survives untouched**. The only behavior that
165
+ changes is auto-advance, which is now off unless asked for:
166
+
167
+ ```jsonc
168
+ // before
169
+ { "config": {
170
+ "mode": "conversational",
171
+ "theme": { "style": "centered-minimal" },
172
+ "navigation": { "transition": "scaleFade" }
173
+ } }
174
+
175
+ // after
176
+ { "config": {
177
+ "theme": { "style": "centered-minimal" },
178
+ "navigation": { "transition": "scaleFade", "autoAdvance": true }
179
+ } }
180
+ ```
181
+
182
+ A flow that had `mode: "standard"` (or no `mode` at all) needs only the field
183
+ deleted. It gains Enter-to-advance on its one-question screens, which was
184
+ previously withheld for no reason anyone could see.
185
+
186
+ `mode` is not read anywhere any more, so leaving a stale `"mode"` key in stored
187
+ JSON is inert rather than harmful — but the schema no longer describes it, and
188
+ `npm run validate` will not vouch for it.
189
+
190
+ ### Updating code
191
+
192
+ ```diff
193
+ -<FBRE flow={flow} mode="conversational" onFlowComplete={done} />
194
+ +<FBRE
195
+ + flow={flow}
196
+ + theme={{ style: "centered-minimal" }}
197
+ + navigation={{ autoAdvance: true }}
198
+ + onFlowComplete={done}
199
+ +/>
200
+ ```
201
+
202
+ ```diff
203
+ -<ThemeEditor mode={value.mode} theme={value.theme} onChange={setValue} />
204
+ +<ThemeEditor theme={value.theme} onChange={setValue} />
205
+ ```
206
+
207
+ The theme editor's `STANDARD_STYLE_OPTIONS`, `CONVERSATIONAL_STYLE_OPTIONS`,
208
+ `styleOptionsForMode` and `reconcileStyle` exports are replaced by a single
209
+ `STYLE_OPTIONS` array; use `styleFamily()` from `fiber-types` if you need the
210
+ partition.
211
+
212
+ ## Related
213
+
214
+ - [Flow Schema → Style Families](../schema/flow-schema.md#style-families)
215
+ - [FBRE Theming Guide](@sonata-innovations/fiber-fbre/docs/features/fbre-theming.md) — palette tokens, brand fonts, and how a
216
+ style's preset interacts with the knobs
217
+ - [FBRE Integration Guide → Focused Presentation](@sonata-innovations/fiber-fbre/docs/integration/fbre.md#focused-presentation)