Open Form

/form-authoring

The technical reference the agent reads before writing or editing a form.

/form-authoring is the spec for everything under forms/. /create-form, /create-theme and /apply-comments defer to it, and AGENTS.md tells the agent to read it before any edit inside forms/ — so "add a phone field" or "make this optional" goes through it too.

You don't usually invoke it directly. Other skills and the rules do.

What it covers

TopicWhat the agent learns
File contractforms/<id>/index.tsx, one <Form>, header text in config, a default export named after the form, render-safe code (the form renders once on the server to derive its schema)
Structure<Form>, <Page>, <When>, <Row>, <Heading>, <Paragraph>, <Submit>, <Success>
FieldsEvery field, what it stores, and its props
Conditions<When> operators (and what they mean on a CheckboxGroup), all / any / not / condition, conditional steps, hidden answers dropped on the server
ValidationBuilt-in rules, validate functions or a Standard Schema, run on client and server alike
Custom fieldsuseField and FieldShell
Live answersuseFieldValue, useFormValues and <Form aside> for previews and totals
Custom stylingA local stylesheet scoped with <Form className>, prefixed classes on --of-* tokens, --of-aside-width, and the .of-* classes to target when restyling what the runtime renders
ConfigSubmission policy, protection, success screen, drafts, time zones; webhooks and embeds in open-form.config.ts
ThemesThe form's own copy in forms/<id>/theme/ and <Form theme>, references in themes/ and the starters, theme copy, tokens, replaceable components, layouts, closed screens (Notice), useFormNav and useFormMessages
Languageslocale, translations, <T>, language-neutral option values
Design guidelinesQuestions: ask less, order easy to hard, 3–7 fields per page, RadioGroup vs. Select vs. cards. Look: each header fact once, one signature element tied to the form's purpose, the theme's palette only, the first question on a phone's first screen, success copy written for the form

The Components and Reference sections document the same API for people.

Anti-patterns

The skill ships a table of things agents reach for by habit, and what to write instead:

Don'tDo
useState + onChange for valuesField components, or useField
{watch('x') === 'y' && …} or ternaries on values<When field="x" equals="y">
onSubmit={() => fetch('/api/…')}Nothing — submission is built in
useFormValues() to show or hide fields<When>; the hooks are for display only
API routes, SQL, Express handlersconfig.submissions / protection
fetch() to Slack or Zapier on submitwebhooks in open-form.config.ts
Hand-written if (!email.includes('@'))<Email>; validate for real custom rules
theme: 'terminal' in defineForm, or importing from themes/open-form theme copy terminal <id> + <Form theme={theme}>
CSS grid or flex to arrange fields<Row>, <Page>
Styling tags (button[type="submit"], h1) or unscoped .of-* rulesThe .of-* classes under [data-of-theme="<id>"]
Hex colors in the form's CSSvar(--of-*) tokens; tints via color-mix()
The date and venue in the description and in an asideEach fact once
A bare "Thanks!" success screenWhat happens next, in the form's voice
Raw <label> / <input> markupDSL components (they handle ARIA, focus, errors)
Renaming a field that already has responsesKeep the name; change the label
Reading window during renderuseEffect

Self-review checklist

Before handing off, the agent checks:

  1. Ids — the folder is kebab-case; every name is unique, camelCase and meaningful.
  2. Required and conditional — only truly necessary fields are required; conditional fields use <When>, not ternaries.
  3. Policy — limits, dates, uniqueness and language match the request, and nothing more was added.
  4. Nothing hand-rolled — no state, fetch, validation or styling outside the theme and form CSS.
  5. The look was chosen — the form has its own copy of the agreed theme in forms/<id>/theme/, imported above the form's CSS and passed to <Form theme>; nothing imports from themes/; copy follows the theme's THEME.md; conversational forms have one question per page. Each header fact appears once, there's at most one signature element, the form's CSS adds no new hues, and the success copy is written for this form.
  6. Custom components spread field.controlProps, attach field.ref, keep native inputs and a visible focus style.
  7. open-form check <id> passes and its steps, fields, conditions, required flags and policy match the plan; pnpm typecheck passes.
  8. Closed screens — the messages are in the form's voice, and a custom Notice handles every state the form can reach without inventing facts.
  9. Translations — locale is set, options have explicit values, every language shows N/N translated, and every "not seen" entry is text shown only for certain answers or a validate message, not a key left behind by edited text.
  10. Seen, not assumed — screenshots of /f/<id> at desktop and phone width match the plan: the title shows, the form's own CSS applies, nothing is low-contrast or clipped, and the first question fits a phone's first screen. Then Studio's Preview: conditional pages through the step buttons, each closed screen under When closed, and each translation through the language picker. Without a browser tool, the agent says it hasn't seen the form.

On this page