Skip to content

feat(templates): add the form wizard page template - #5664

Merged
ernestt merged 3 commits into
core-stepper-narrow-collapsefrom
template-form-wizard
Sep 5, 2026
Merged

feat(templates): add the form wizard page template#5664
ernestt merged 3 commits into
core-stepper-narrow-collapsefrom
template-form-wizard

Conversation

@ernestt

@ernestt ernestt commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

A linear multi-step form: one panel at a time, a horizontal progress track pinned in the header so it never scrolls away, and per-step validation that gates each advance while letting people move backward freely.

Stacked on #5659 — this template leans on the core Stepper collapsing itself in narrow containers rather than hand-rolling a fallback, so it should land after that one.

Why it is built this way

One rule set. Validation is a pure function of the field values, so the same errors object gates the Next button, tints the stepper, and fills the review step's issue list. There is no second copy of the rules to drift. Errors stay silent until a step has been attempted, so an untouched field is never pre-flagged red.

Backward movement is free. onStepClick is passed unconditionally, so every step is reachable at any time. A wizard that locks you out of step 1 because step 3 is invalid turns a typo into a restart. Forward movement is still gated by the footer.

Skip is not Next with another label. Next asserts the step is done and validates it; Skip declines the step outright and clears what it collects. A half-filled optional step riding along invisibly is the bug that button exists to prevent.

Screenshots

Radio list for the join policy, with the step counter centred in the footer:

Workspace step

Skip appears only on the one optional step, at the far edge from the button it would otherwise compete with:

Team step with Skip

The running total is a muted card — the consequence of every control above it, so it reads as its own object:

Plan step cost card

Review reads top to bottom in one column, against the steps that produced it:

Review step

When a step is blocked, the error takes the centre slot rather than the counter. They never need to be read at the same time, and the header stepper is still showing position:

Blocked state

A step left broken stays flagged, and the review step surfaces it rather than letting it hide behind a panel:

Outstanding issue banner

At 420px the core Stepper collapses itself and the footer holds its three zones:

Narrow width

Audit

97/100, grade A, under template rubric 1.3.

Category Score
Component purity 30/30 — zero raw HTML
Icon purity 15/15
Custom CSS 12/15
Layout & structure 15/15
Doc metadata 10/10
Image handling 5/5
Code quality 10/10

The three points are one custom declaration: a zero flex basis on the footer's two end zones. That is what holds the counter on the centre line when the primary button's label grows from "Next" to "Create workspace" on the last step — measured at 0px offset on every step. StackItem exposes flex-grow but not basis, so there is no component prop to use instead.

Test plan

  • All four steps render, validation gates each advance
  • Skip clears the optional step and advances
  • Counter stays centred across every step and both button labels
  • Zero console errors at 1200px and 420px
  • ESLint clean, no dead code

Made with Cursor

A linear multi-step form: one panel at a time, a horizontal progress track
pinned in the header so it never scrolls away, and per-step validation that
gates each advance while letting people move backward freely.

Validation is a pure function of the field values, so one rule set gates the
Next button, tints the stepper, and fills the review step's issue list — there
is no second copy of the rules to drift. Errors stay silent until a step has
been attempted, so an untouched field is never pre-flagged.

The footer carries three zones: Skip on the far edge for the one optional step,
the step counter centred, and Back/Next together. Skip is not Next with another
label — it clears what the optional step collects rather than letting a
half-filled step ride along invisibly.

Audited at 97/100 (grade A) under template rubric 1.3. The three points are the
one custom declaration, a zero flex basis on the footer's end zones, which is
what holds the counter on the centre line when the primary button's label grows
on the last step. StackItem exposes flex-grow but not basis, so there is no
component prop to use instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Sep 3, 2026 9:09am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 28, 2026
The footer carried a count of how many problems were above, which was a second
copy of a message every offending field was already showing and never said
which field. Pressing Next on a blocked step now sends focus to the first field
that needs attention instead: the browser scrolls it into view, and the error
the field is already rendering is its accessible description, so a screen
reader hears exactly which field and why rather than a number.

That frees the footer's centre slot, so the step counter is visible at every
moment rather than being displaced whenever a step is blocked.

`validate` records each step's problems in the order the fields appear, so the
first key is the first thing on the page to fix — no second ordering to keep in
sync with the form.

Considered showing the summary as a toast and rejected it: Toast's own docs say
not to use toasts for form validation errors, and to prefer inline validation
so the user can see exactly which field needs fixing. This is that.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ernestt

ernestt commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Update: blocked Next now moves focus instead of summarising

Followed up on the footer's blocked-step message. It previously read "One problem above needs fixing first." in the centre slot, displacing the step counter.

I looked at showing it as a toast and decided against it — Toast's own docs carry two don'ts that both apply:

Don't show form validation errors as toasts. Use inline field validation so the user can see exactly which field needs fixing.

Don't use a toast for critical errors that block the user.

So the summary is gone entirely and Next now does what those docs point at: focus moves to the first field that needs attention. The browser scrolls it into view, and the error the field is already rendering becomes its accessible description, so a screen reader hears the specific field and reason rather than a count.

The counter is now visible at every moment:

form-wizard blocked final step

Verified with Playwright across all four steps:

Case Focus lands on Announced
Step 1, name blank Workspace name "Enter a name for the workspace."
Step 1, name fixed / URL bad Workspace URL "Use lowercase letters, numbers, and hyphens…"
Step 4, terms unchecked Terms checkbox "Accept the terms to create the workspace."

Each focused control reports aria-invalid="true" with the message wired through aria-describedby, and all three scroll into view. Valid steps still advance. No console errors.

Ordering needs no separate list: validate records each step's problems in field order, so the first key is the first thing on the page to fix.

@xinzi-bot xinzi-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—the focus-on-error update is good. One validation gap remains: leaving a broken step backward does not mark it attempted. Review can then hide that error, and the final handler checks only the current step. Please make the validation state cover every path through the Stepper.

[Automated review]

@cixzhang cixzhang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—the focused-error path works: the field receives focus with aria-invalid and its error description. Three blockers remain:

  1. The wizard has no authoritative completion path. Moving backward from an invalid Plan, then jumping to Review, hides the error; Create checks only Review. There is also no <form>/submit path, so Enter and Create submit nothing, while Cancel is inert.
  2. The Plan picker exposes independent checkboxes under an unassociated label, not one labeled single-choice group. Price formatting also follows the browser locale instead of Astryx’s provider locale.
  3. At 320px, “2 of 4” overlaps Back by 13.8px in both LTR and RTL.

Please make every allowed path preserve whole-wizard validity, use native form/group semantics, provider-backed formatting, and a collision-free 320px footer.

[Reviewed by Robohands]

@ernestt

ernestt commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Release-process check: this PR adds a consumer-visible template under the publishable @astryxdesign/cli 0.5.x package, but no .changeset/*.md file is present. It needs a patch entry whose first body line is a [feat] user-facing summary ending in (#5664) and whose second body line is @ernestt.

@ernestt
ernestt merged commit 0c0befc into core-stepper-narrow-collapse Sep 5, 2026
4 checks passed
ernestt added a commit that referenced this pull request Sep 5, 2026
* feat(templates): add the form wizard page template

A linear multi-step form: one panel at a time, a horizontal progress track
pinned in the header so it never scrolls away, and per-step validation that
gates each advance while letting people move backward freely.

Validation is a pure function of the field values, so one rule set gates the
Next button, tints the stepper, and fills the review step's issue list — there
is no second copy of the rules to drift. Errors stay silent until a step has
been attempted, so an untouched field is never pre-flagged.

The footer carries three zones: Skip on the far edge for the one optional step,
the step counter centred, and Back/Next together. Skip is not Next with another
label — it clears what the optional step collects rather than letting a
half-filled step ride along invisibly.

Audited at 97/100 (grade A) under template rubric 1.3. The three points are the
one custom declaration, a zero flex basis on the footer's end zones, which is
what holds the counter on the centre line when the primary button's label grows
on the last step. StackItem exposes flex-grow but not basis, so there is no
component prop to use instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(templates): answer a blocked Next with focus, not a footer summary

The footer carried a count of how many problems were above, which was a second
copy of a message every offending field was already showing and never said
which field. Pressing Next on a blocked step now sends focus to the first field
that needs attention instead: the browser scrolls it into view, and the error
the field is already rendering is its accessible description, so a screen
reader hears exactly which field and why rather than a number.

That frees the footer's centre slot, so the step counter is visible at every
moment rather than being displaced whenever a step is blocked.

`validate` records each step's problems in the order the fields appear, so the
first key is the first thing on the page to fix — no second ordering to keep in
sync with the form.

Considered showing the summary as a toast and rejected it: Toast's own docs say
not to use toasts for form validation errors, and to prefer inline validation
so the user can see exactly which field needs fixing. This is that.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: fix PR #5664 release metadata

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions
github-actions Bot deleted the template-form-wizard branch September 5, 2026 06:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants