BorgIQ Form Builder
Build forms and interface pages — the primary user-facing surface in BorgIQ workflows. Pair this skill with the hub borgiq-builder (which handles flow wiring) and, when fields produce or consume structured data, borgiq-json-schema-builder.
Mental model
Interface pages are the form surface of BorgIQ. Two actors share one page-configuration schema:
| Actor |
When it fires |
Use it for |
| InterfaceTriggerActor |
Page submission starts the flow |
Public-facing forms: signups, feedback, intake, surveys |
| InterfaceActor |
Renders mid-flow with two output ports — meta (URL on render) and event (on submission) |
Approvals, async review, "send a form via email" patterns |
Interfaces are forms, not standalone apps. Using an interface with no form fields (just display + webViewer) is no longer recommended — for interactive dashboards, SPAs, or bespoke layouts, build an app instead (see the borgiq-react-app-builder skill). The webViewer component embeds an external page or custom HTML inside a form, but it does not replace forms.
Key decisions
- InterfaceTrigger vs InterfaceActor. Does the form start the flow (Trigger) or sit inside it (Actor)? An InterfaceActor's
meta port emits a URL that you can route to email/Slack and event fires when the user submits — this is the canonical async-approval pattern.
- Form width.
formWidth: full for data-dense layouts; half for focused single forms; third for very simple inputs (a single email field, a yes/no).
- Single page vs multi-step. Group within one page using
section and collapse. Reach for a wizard (chained InterfaceActors or onSubmit: nextInterface) only when steps depend on prior answers or the form is long enough that users would abandon a single page.
- Validation strategy.
required: true for mandatory, readOnly: true for display-only context, conditional fields for branching logic. Validation is client-side at submission.
- Theme. Native page chrome is themed by the page config (
themeColor, backgroundColor) — pick from themes.md (10 pre-built — Modern Minimalist, Ocean Depths, etc.). Custom HTML inside a webViewer is themed differently: use the app theme library (react-app-themes.md, default hearth) — it's plain CSS, no React required.
- webViewer scope. Use it to embed external pages or hand-written HTML/CSS within the form (e.g., a help panel, a third-party widget). Don't try to rebuild the form itself in webViewer — use the native components.
Workhorse components
Reach for these first; they cover ~80% of forms.
| Component |
Use it for |
text |
Single-line input — names, titles, short answers |
textarea |
Multi-line — descriptions, notes, longer feedback |
select |
Dropdown choice when there are 4+ options |
radio |
Mutually exclusive choice with 2-4 options — cleaner than select here |
checkbox |
Single boolean — terms acceptance, opt-in, toggles |
fileInput / fileDropzone |
Single file or multi-file drag-and-drop upload |
section |
Group related fields; set extendParentObject: true to flatten into the parent payload |
header |
Title/divider text — establishes visual hierarchy |
markdown |
Rich instructional content (formatted help, intros) |
formButton / button |
Submit or action — required to trigger the workflow |
See references/interface-pages.md for the full 40+ component catalog, including selection (multiSelect, chips), date/time, arrays, dynamic defaults, and conditional fields.
Anti-patterns
- Interfaces as standalone apps. No form fields, just webViewer/markdown for display? That's a deprecated pattern — build an app via the
borgiq-react-app-builder skill instead.
- Arbitrary spacing or hex colors. The theme's spacing scale and tokens are the contract. Hard-coded
padding: 17px or color: #4A90E2 outside the theme is a smell.
- Missing interaction states. Every button needs hover/focus/disabled; every list needs loading/empty/error. Forms without states feel like screenshots.
- Sidebar with a different background color. Fragments the visual space. Same background, subtle border for separation.
- Skipping the squint test. Blur your eyes — if the hierarchy isn't visible and surfaces aren't distinguishable, the design isn't intentional yet.
Theming, briefly
Two surfaces, two mechanisms:
- Native page + form components are themed by the page config (
themeColor, backgroundColor); pick a palette from references/themes.md. Dark mode is supported via data-theme="dark".
- Custom HTML inside a
webViewer uses the app theme library — references/react-app-themes.md. It's framework-agnostic CSS (custom properties + class recipes), so it works in the webViewer's raw HTML+CSS with no React and no CDN: paste the Base Contract + one theme block (default hearth, or the theme closest to the page's palette) into the webViewer's styles, build markup on the recipe classes, and use Tabler icons as inline SVG with stroke="currentColor". Never hard-code colors — tokens only, and dark mode comes wired in.
Wiring to downstream actors
Form submissions are emitted as structured data; downstream actors consume specific fields:
# Downstream HttpRequestActor accessing form data
inputs:
customerName: ${{ msg.signup_form.body.fullName }}
customerEmail: ${{ msg.signup_form.body.email }}
The body keys come from your key: declarations on each component in the page config. Define them with care — they're the API contract between the form and the rest of the flow.
References
| File |
What's inside |
references/interface-pages.md |
Full page schema, all 40+ component types, dynamic defaults, conditional fields, onSubmit behavior, design guidelines |
references/interface-trigger-actor.md |
InterfaceTriggerActor config, emitted message schema, downstream field access |
references/interface-actor.md |
InterfaceActor config, two-port pattern, async approval workflows |
references/react-app-themes.md |
The app theme library for custom HTML in webViewer: token contract, base stylesheet, component recipes, five theme skins, Tabler icon rules |
references/themes.md |
10 pre-built themes with palettes, typography, and best-use contexts |
references/typescript/form-components.md |
TypeScript/Zod schemas for every form component |
When to hand off to other spokes
| Customer ask |
Hand off to |
| "I need a custom-styled dashboard / data explorer / SPA" |
borgiq-react-app-builder (ReactAppTriggerActor) |
| "The form fields should match a JSON schema" or "validate against this contract" |
borgiq-json-schema-builder |
| "Wire the form to an AI agent / tool-using LLM" |
borgiq-agent-builder |
| Anything about edges, msgVars, fork/join, deploy |
Hub: borgiq-builder |
1---2name: borgiq-form-builder3description: Design BorgIQ interface pages, forms, form components, validation rules, conditional fields, themes, and the web-viewer embed. This is the primary BorgIQ UI surface. Use whenever the user is building an interface page, form, signup, data-entry UI, approval workflow, survey, or wants to embed another page inside an interface. Triggers on "build a form", "interface page", "approval flow", "data entry", "signup form", "survey", "feedback form", "InterfaceTriggerActor", "InterfaceActor".4---56# BorgIQ Form Builder78Build forms and interface pages — the primary user-facing surface in BorgIQ workflows. Pair this skill with the hub `borgiq-builder` (which handles flow wiring) and, when fields produce or consume structured data, `borgiq-json-schema-builder`.910## Mental model1112Interface pages are the **form surface** of BorgIQ. Two actors share one page-configuration schema:1314| Actor | When it fires | Use it for |15|---|---|---|16| **InterfaceTriggerActor** | Page submission **starts** the flow | Public-facing forms: signups, feedback, intake, surveys |17| **InterfaceActor** | Renders **mid-flow** with two output ports — `meta` (URL on render) and `event` (on submission) | Approvals, async review, "send a form via email" patterns |1819**Interfaces are forms, not standalone apps.** Using an interface with no form fields (just display + webViewer) is no longer recommended — for interactive dashboards, SPAs, or bespoke layouts, build an app instead (see the `borgiq-react-app-builder` skill). The `webViewer` component embeds an external page or custom HTML *inside* a form, but it does not replace forms.2021## Key decisions22231. **InterfaceTrigger vs InterfaceActor.** Does the form *start* the flow (Trigger) or sit *inside* it (Actor)? An InterfaceActor's `meta` port emits a URL that you can route to email/Slack and `event` fires when the user submits — this is the canonical async-approval pattern.242. **Form width.** `formWidth: full` for data-dense layouts; `half` for focused single forms; `third` for very simple inputs (a single email field, a yes/no).253. **Single page vs multi-step.** Group within one page using `section` and `collapse`. Reach for a wizard (chained InterfaceActors or `onSubmit: nextInterface`) only when steps depend on prior answers or the form is long enough that users would abandon a single page.264. **Validation strategy.** `required: true` for mandatory, `readOnly: true` for display-only context, conditional fields for branching logic. Validation is client-side at submission.275. **Theme.** Native page chrome is themed by the page config (`themeColor`, `backgroundColor`) — pick from `themes.md` (10 pre-built — Modern Minimalist, Ocean Depths, etc.). Custom HTML inside a `webViewer` is themed differently: use the app theme library (`react-app-themes.md`, default `hearth`) — it's plain CSS, no React required.286. **webViewer scope.** Use it to embed external pages or hand-written HTML/CSS *within* the form (e.g., a help panel, a third-party widget). Don't try to rebuild the form itself in webViewer — use the native components.2930## Workhorse components3132Reach for these first; they cover ~80% of forms.3334| Component | Use it for |35|---|---|36| `text` | Single-line input — names, titles, short answers |37| `textarea` | Multi-line — descriptions, notes, longer feedback |38| `select` | Dropdown choice when there are 4+ options |39| `radio` | Mutually exclusive choice with 2-4 options — cleaner than `select` here |40| `checkbox` | Single boolean — terms acceptance, opt-in, toggles |41| `fileInput` / `fileDropzone` | Single file or multi-file drag-and-drop upload |42| `section` | Group related fields; set `extendParentObject: true` to flatten into the parent payload |43| `header` | Title/divider text — establishes visual hierarchy |44| `markdown` | Rich instructional content (formatted help, intros) |45| `formButton` / `button` | Submit or action — required to trigger the workflow |4647See [`references/interface-pages.md`](../borgiq-builder/references/interface-pages.md) for the full 40+ component catalog, including selection (`multiSelect`, `chips`), date/time, arrays, dynamic defaults, and conditional fields.4849## Anti-patterns50511. **Interfaces as standalone apps.** No form fields, just webViewer/markdown for display? That's a deprecated pattern — build an app via the `borgiq-react-app-builder` skill instead.522. **Arbitrary spacing or hex colors.** The theme's spacing scale and tokens are the contract. Hard-coded `padding: 17px` or `color: #4A90E2` outside the theme is a smell.533. **Missing interaction states.** Every button needs hover/focus/disabled; every list needs loading/empty/error. Forms without states feel like screenshots.544. **Sidebar with a different background color.** Fragments the visual space. Same background, subtle border for separation.555. **Skipping the squint test.** Blur your eyes — if the hierarchy isn't visible and surfaces aren't distinguishable, the design isn't intentional yet.5657## Theming, briefly5859Two surfaces, two mechanisms:6061- **Native page + form components** are themed by the page config (`themeColor`, `backgroundColor`); pick a palette from [`references/themes.md`](../borgiq-builder/references/themes.md). Dark mode is supported via `data-theme="dark"`.62- **Custom HTML inside a `webViewer`** uses the app theme library — [`references/react-app-themes.md`](../borgiq-builder/references/react-app-themes.md). It's framework-agnostic CSS (custom properties + class recipes), so it works in the webViewer's raw HTML+CSS with no React and no CDN: paste the Base Contract + one theme block (default `hearth`, or the theme closest to the page's palette) into the webViewer's styles, build markup on the recipe classes, and use Tabler icons as inline SVG with `stroke="currentColor"`. Never hard-code colors — tokens only, and dark mode comes wired in.6364## Wiring to downstream actors6566Form submissions are emitted as structured data; downstream actors consume specific fields:6768```yaml69# Downstream HttpRequestActor accessing form data70inputs:71 customerName: ${{ msg.signup_form.body.fullName }}72 customerEmail: ${{ msg.signup_form.body.email }}73```7475The `body` keys come from your `key:` declarations on each component in the page config. Define them with care — they're the API contract between the form and the rest of the flow.7677## References7879| File | What's inside |80|---|---|81| [`references/interface-pages.md`](../borgiq-builder/references/interface-pages.md) | Full page schema, all 40+ component types, dynamic defaults, conditional fields, onSubmit behavior, design guidelines |82| [`references/interface-trigger-actor.md`](../borgiq-builder/references/interface-trigger-actor.md) | InterfaceTriggerActor config, emitted message schema, downstream field access |83| [`references/interface-actor.md`](../borgiq-builder/references/interface-actor.md) | InterfaceActor config, two-port pattern, async approval workflows |84| [`references/react-app-themes.md`](../borgiq-builder/references/react-app-themes.md) | The app theme library for custom HTML in webViewer: token contract, base stylesheet, component recipes, five theme skins, Tabler icon rules |85| [`references/themes.md`](../borgiq-builder/references/themes.md) | 10 pre-built themes with palettes, typography, and best-use contexts |86| [`references/typescript/form-components.md`](../borgiq-builder/references/typescript/form-components.md) | TypeScript/Zod schemas for every form component |8788## When to hand off to other spokes8990| Customer ask | Hand off to |91|---|---|92| "I need a custom-styled dashboard / data explorer / SPA" | `borgiq-react-app-builder` (ReactAppTriggerActor) |93| "The form fields should match a JSON schema" or "validate against this contract" | `borgiq-json-schema-builder` |94| "Wire the form to an AI agent / tool-using LLM" | `borgiq-agent-builder` |95| Anything about edges, msgVars, fork/join, deploy | Hub: `borgiq-builder` |