Form UX skill for Codex
Forms people finish on the first try. Codex audits every field before building, uses a single column with visible labels, sets type, autocomplete and inputmode so autofill and mobile keyboards work, and validates at the right moment instead of on the first keystroke. Errors are specific, linked with aria-describedby and summarized on submit, and multi-step flows keep progress and offer a review step.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- form-ux Installs to ~/.agents/skills/form-ux/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install form-ux The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project form-ux Installs to .agents/skills/form-ux/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/form-ux.zipunzip -o form-ux.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) form-ux PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $form-ux, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Designs forms people finish - visible labels, single-column layout, the right input types, autocomplete and inputmode tokens, validation timing (on blur, then live after an error), accessible inline errors with aria-describedby and an error summary, multi-step flows with saved progress and a review step, password, date, address and phone patterns, and submit, pending and success states. Use when building or reviewing signup, checkout, settings or onboarding forms, when form completion or error rates are poor, or when asked how and when to show validation errors.
Use it when
- Building signup, checkout, settings or application forms
- Deciding validation timing and writing error messages
- Splitting a long form into steps
- Autofill, mobile keyboards or screen readers misbehave on a form
Use something else when
- Wiring the form state and schema in React code: use react-hook-form-zod
- Copy for loading, success and disabled states across the app: use ui-copy-states
How to check the result
Submitting an empty form focuses a summary whose links reach each field, errors clear as soon as they are fixed, and browser autofill fills the right inputs.
The mistake it prevents
Disabling the submit button until the form is valid, which hides why it cannot be submitted.
A form is right when people can complete it without guessing what a field wants, errors appear at the moment they are useful and say how to fix them, nothing they typed is lost (on error, on back, on refresh), and it works with keyboard, screen reader, password manager and autofill. The best field is the one you removed.
Before designing
- Read
AGENTS.mdand the existing form stack (React Hook Form, Formik, server actions, native forms) and the field components in the design system. - List every field and justify it: who uses the value, and can it be inferred, deferred to later, or made optional? Remove or defer what you can.
- Note server-side validation rules; client rules must mirror them, never replace them.
Layout rules
- One column. Multi-column forms cause skipped fields; exceptions are tightly related short pairs (city + postcode, expiry + CVC).
- Label above the input, always visible. Placeholder is for an example format at most, never the label.
- Field width hints the expected length: postcode short, street full width.
- Mark the minority: if most fields are required, mark optional ones “(optional)”; otherwise mark required ones. Do not rely on a red asterisk alone.
- Group with
<fieldset>+<legend>(shipping address, payment). Radio groups and checkbox groups always use fieldset. - Primary button left-aligned with the fields (or full width on mobile), labelled with the outcome: “Create account”, “Pay $48.00”, not “Submit”.
Input types and autofill
| Field | Markup |
|---|---|
type="email" autocomplete="email" spellcheck="false" | |
| New password | type="password" autocomplete="new-password" + show/hide toggle |
| Current password | autocomplete="current-password" |
| One-time code | autocomplete="one-time-code" inputmode="numeric" (not type="number") |
| Name | autocomplete="name" (or given-name/family-name only if truly needed) |
| Phone | type="tel" autocomplete="tel"; accept any formatting, normalize on server |
| Address | autocomplete="address-line1", address-level2 (city), postal-code, country |
| Card number | autocomplete="cc-number" inputmode="numeric"; let the payment provider’s field handle it when possible |
| Quantity, age | inputmode="numeric" text input; type="number" scroll-wheel and spinner bugs bite |
| Date of birth | Three text fields (day, month, year) or one with a clear format; not a calendar picker |
| Date near today | Native type="date" or an accessible picker with typed input allowed |
Never block paste (passwords, codes, emails). Never disable autofill.
Validation timing
| Moment | Do |
|---|---|
| While typing, field not yet in error | Nothing. Do not shout “invalid email” at the first character |
| On blur | Validate format of that field; show error if wrong |
| After a field has shown an error | Re-validate on each input so the error clears as soon as it is fixed |
| On submit | Validate everything; show error summary; move focus to it |
| Server response | Map server errors to fields; general errors go in the summary |
Exceptions: password strength rules and username availability can show live progress as positive guidance, not errors.
Error markup
<div class="field">
<label for="email">Work email</label>
<p id="email-hint" class="hint">We'll send the invoice here.</p>
<input id="email" name="email" type="email" autocomplete="email"
aria-describedby="email-hint email-error" aria-invalid="true">
<p id="email-error" class="error">
<svg aria-hidden="true" class="icon"><use href="#icon-alert"/></svg>
Enter an email address like [email protected]
</p>
</div>
- Error text says what to do, specific to the problem: “Enter a date in the past”, not “Invalid input”. Keep the user’s input; never clear a field on error.
- Show error below the input (or between label and input), with icon plus color.
- Set
aria-invalid="true"only when in error; remove it when fixed. - Use
novalidateon the form when you render your own messages, so browser bubbles do not compete, but keeprequiredandtypefor semantics.
Error summary on submit, focused so screen readers announce it (focus alone announces it;
adding role="alert" as well makes some readers announce it twice):
<div id="error-summary" tabindex="-1" aria-labelledby="error-summary-title">
<h2 id="error-summary-title">There are 2 problems with your details</h2>
<ul>
<li><a href="#email">Enter an email address like [email protected]</a></li>
<li><a href="#postcode">Enter a postcode</a></li>
</ul>
</div>
form.addEventListener("submit", (e) => {
const errors = validate(form); // project validator
if (errors.length) {
e.preventDefault();
renderSummary(errors);
document.getElementById("error-summary").focus();
}
});
For short forms (1–3 fields), focusing the first invalid field instead of a summary is
fine. Implementation with React Hook Form and Zod: react-hook-form-zod.
Multi-step forms
Split when the form exceeds roughly 7–10 fields or has distinct topics; keep it on one page when fields depend on each other heavily.
- One topic per step; title says what it is (“Delivery address”).
- Progress indicator with step names, not just dots: “Step 2 of 4: Delivery”.
- Validate each step before advancing; never show errors from steps not yet visited.
- Back never loses data. Persist progress (server draft or
sessionStorage) so a refresh does not restart. Do not store payment or sensitive data client-side. - Final review step with “Change” links back to each step.
- Do not ask for the same information twice (WCAG 2.2 SC 3.3.7 Redundant Entry): “Billing address same as delivery” checkbox, prefilled values.
Submission states
- Pending: keep the button enabled-looking but ignore repeat clicks (or
aria-disabledplus spinner and “Creating account…” label). Prevent double submit on the server too (idempotency key). - Do not disable the submit button until the form is valid; users cannot discover why it is disabled. Let them submit and show the errors.
- Success: confirm what happened and what is next; move focus to the confirmation heading. For destructive or payment actions, show a confirmation step before submit.
- Network failure: keep all input, show a retry, explain whether anything was saved.
Specific patterns
- Passwords: show requirements before typing, show/hide toggle, allow paste and password managers, no composition rules beyond length when policy allows (NIST 800-63B favors length and breach checks).
- Authentication: do not require cognitive tests like transcribing characters without alternatives (WCAG 2.2 SC 3.3.8); support passkeys or magic links if the product does.
- Long selects: over ~15 options, use a combobox with type-to-filter.
- Checkbox for consent: unchecked by default, label is the full statement.
Workflow
- [ ] 1. Field audit: remove, defer or infer
- [ ] 2. Choose single page vs multi-step
- [ ] 3. Markup: labels, types, autocomplete, fieldsets
- [ ] 4. Validation rules shared with server; timing per the table
- [ ] 5. Error messages written per field and failure
- [ ] 6. Pending, success, network-failure states
- [ ] 7. Verify with keyboard, screen reader, autofill and mobile keyboard
Verification
- Tab through the whole form: logical order, visible focus, every control reachable.
- Submit empty: summary appears and is focused; each link moves to its field.
- Fix one error: it clears immediately; others remain.
- Autofill from the browser and a password manager fills the right fields.
- On a phone (or
--device "iPhone 13"in Playwright), numeric fields open the number pad and email fields the email keyboard. - Screen reader announces label, hint and error for an invalid field.
- Refresh mid-way through a multi-step form: progress is kept.
Output contract
**Fields:** <n> (removed <list>, deferred <list>)
**Structure:** single page | <n> steps: <names>
**Validation:** on blur, live after error, summary on submit; rules shared via <schema path>
**Error messages:** <file or table>
**States:** pending, success, network failure, server field errors
**Checks:** keyboard ok, VoiceOver/NVDA ok, autofill ok, mobile keyboards ok What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Form UX"
short_description: "Forms people finish on the first try"
default_prompt: "$form-ux Our signup form has a 40% drop-off, review it and fix the UX" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.