Open Form

/create-form

The /create-form skill takes a one-line request to a reviewed, working form: it asks what it needs, proposes three looks and writes the form.

/create-form is the entry point for any new form. It owns the workflow; /form-authoring supplies the how. The agent writes forms/<id>/, including the form's own look in forms/<id>/theme/, and themes/<id>/ only when the look you pick needs a new reference theme.

Create a registration form for our developer meetup. Name, email, role and
years of experience; if they're a student, ask for their school. 100 seats.

What it does

Understand the form

Purpose and audience, must-have questions, follow-ups that depend on earlier answers, policy (limits, dates, one response per email or device), languages, what happens after submit, and the header facts. If the purpose or the core questions are unclear, it asks first; otherwise it restates its reading in one line and moves on. It never invents dates, venues or prices, and asks when the facts disagree, such as a date and a weekday that don't match.

It also checks the brief against what Open Form can't do yet (file uploads, a quiz that scores itself, a repeating group per guest, emails to respondents, sign-in, payments) and tells you before building, with the workaround it would use: a link field instead of an upload, answers scored after export, one set of fields per guest up to a count you confirm. A form filled in on one shared network, like a QR code in a shop, gets a higher protection.rateLimit.

Propose three visual directions

It first lists a handful of things respondents know from this moment (for a café's feedback card: the stamp card, the receipt, the chalkboard menu), drops the most literal one, and builds each direction from a different item. It runs open-form theme list, reads the candidates' THEME.md (the project's references in themes/ and the starters) as bases to adapt, and offers three directions tailored to the form and audience, steering clear of the looks generated pages fall into by default (cream with a serif and terracotta, black with one neon accent, # and → as decoration). Each is a theme plus a composition and concrete cues — terminal: CLI prompts, green on black, the agenda printed beside the questions, or brutalist + StepperLayout: loud yellow, thick borders, a step rail — never a bare "modern". When the form has one, a direction also names its signature element: the one piece of UI that belongs to this form, best on the control for its key question (an RSVP answered by circling 出席, a rating stamped onto a loyalty card), otherwise a pass that fills in as people answer, an event's facts or agenda, or plan tiles with a running total. The ambition follows the moment. A quick form, such as a QR feedback card or an internal sign-up, keeps a plain column in all three directions. Invitations, registrations and applications differ in composition too, meaning how the page is arranged around the questions: a full-page poster, a hero beside the form, a wide document sheet, a form with a sticky summary, a step rail, or one question per screen, which it suggests for an NPS survey or a quiz. At most one is the plain centered card. Each direction also says its color scheme: following the device's light and dark (most forms), or light only or dark only when the look depends on one ground, like a terminal or a paper document. When that's still open, it asks in the same question. A brand theme in themes/ is the recommended option. If you already named the look, it skips the question.

A project theme or a starter can be followed closely, or adapted for this form — recolored, with other fonts or another layout — since the form's look is written for it. A new look goes through /create-theme first, which adds it to themes/.

Pick an id

Kebab-case, short, descriptive: meetup-registration, beta-waitlist. The id is the public URL /f/<id>, so the agent avoids internal names and checks forms/ for collisions.

Plan the structure

Shaped by the direction: one question per page for conversational themes and FocusLayout, a single page for up to about seven fields, 3–7 fields per <Page> for longer forms. Easy questions first, free text near the end, follow-ups right after the question they depend on, and name, email and phone together on one page. A form with several pages says how many steps and about how long in its description, and long ones keep drafts. It also plans the look: where each header fact goes (once), where the signature element lives and how the success screen echoes it, what a phone shows on the first screen, and success copy that uses the person's answers. Then it reviews the plan, asking of each choice whether it would have made the same one for a different audience, and replaces the parts that are defaults. Copy follows the theme's tone, locale matches the language, and the closed screen gets messages in the form's voice. A form tied to a date, with limited spots or one response per person, or with a strong look also gets its own Notice: the closed screen in the form's look, saying what to do next with only facts you gave.

Scaffold it from the theme

open-form create <id> --theme <theme> writes a starter forms/<id>/index.tsx and forms/<id>/theme/index.ts with the chosen theme's tokens, already wired up. Only the tokens and the web fonts they name come in; the theme's CSS and components stay with the reference. From then on the form owns its look: changing the theme in themes/ never changes it.

Write the form

The starter's body is replaced: config = defineForm({ … }) — only the policy you asked for — and a default component that renders <Form theme={theme}>, with the theme imported from ./theme/ above the form's own CSS. The component is named after the form (MeetupForm). Named like an Open Form export, such as Form or Email, it would render itself, so open-form check refuses it; a global name such as Date would shadow the global. The agent then writes the rest of the look in forms/<id>/theme/, reading the reference's THEME.md and code and taking only what this form renders: tokens first, then CSS on the runtime's own classes, then a component only for markup or behavior CSS can't give, such as a window frame, a custom Layout for the composition, or keyboard shortcuts. Borrowed classes get the form's own prefix, and a look written for English (uppercase labels, italic help) is adapted for Chinese or Japanese text. After color changes it runs open-form theme check <id> --form. Multilingual forms get a forms/<id>/locales/<lang>.json per extra language.

Self-review

It runs the checklist, then:

npx open-form check <id>
npm run typecheck                   # tsc --noEmit

open-form check loads the form the way the server does and prints its steps, fields, conditions and policy, or the exact error. For a multilingual form it shows each language as N/N translated; the agent fixes every missing entry, and keeps a "not seen" one only when it's text shown for certain answers or a validate message.

check proves the form works, not that it looks right, so the agent then looks at it with whatever browser tool it has, starting the dev server in the background if it isn't running. It takes screenshots of /f/<id> at desktop and phone width and compares them with the direction: the composition reads as planned at desktop width, the title shows, the form's own CSS applies, nothing is low-contrast, clipped or overlapping (on a translated form, nothing sits under the language picker in the top-right corner), and on a phone the first question is on the first screen. A form that follows the device gets the same shots again in dark mode. It also sends one test response to see the success screen, which Studio can't preview, and checks that it echoes the form's signature element. It opens the Schema tab in Studio (/studio/<id>/schema) to compare the derived steps, fields, required flags and conditions with its plan, and Preview for every step and every closed screen under When closed. It fixes what it sees and looks again. Without a browser tool it says it hasn't seen the form, and asks you to open it.

Hand off

You get the public link and the Studio link, the theme, layout and signature element it built, what its plan review changed, whether it looked at the form on desktop and phone, the policy it applied, what respondents see once the form closes, and any assumptions it made, including option lists it wrote itself and limits it worked around. For a multilingual form: the languages, how respondents get theirs (automatically, the picker or ?lang=), and that editing text means updating each locales/*.json. The look is the form's own, in forms/<id>/theme/, for you or the agent to change, or with Adjust in Studio's preview; themes/ is the reference library for new forms, and changing it won't touch this one.

What it asks you

It asks before writing code, and only about what it can't decide well itself:

  • The form itself, when the purpose or core questions are unclear.
  • Limits, when the brief needs something Open Form can't do, with the workaround it suggests.
  • The look — three directions with different compositions, the best fit marked Recommended, plus room for your own answer.
  • In the same question, anything else that changes the build: the flow (one page, sections, or one question per screen) when it's a real choice; policy gaps such as "one registration per email?" when there's a response limit; languages when the audience plausibly reads more than one.

Field types, ordering and labels it decides on its own.

Example prompts

Make a post-purchase NPS survey: a 0–10 score, then ask what we could do
better if they gave 6 or less.
We need a beta waitlist: email, company, team size. Close it on 2026-11-30,
one signup per email. Use our brand theme.
A product feedback form for our users in Taiwan and Japan.

Preview submissions are stored for real. Delete them under Responses.

On this page