FORM WIZARD: Multi-step form a parent can complete without losing work or submitting twice
COMPOSES: .steps + steps_controller.js | .popup + popup_controller.js (discard-confirm) | .inline-notification-error (error summary) | .btn-loading + .spinner-border (submit guard) | form suite
This is one fully interactive instance — try each behaviour directly. Back preserves
data: fill in Step 1, click Continue, then Back — your answers are still there (panels
are never destroyed, only hidden). Validation-blocked advance: leave a required
field empty and click Continue — an error summary appears above the step title, focus moves to
it, and the failing step gets a red exclamation mark in the rail. Discard-confirm:
type something, then click Cancel — a popup asks you to confirm; Escape/overlay-click/"Keep
editing" all resolve to keeping your answers, only "Discard" clears them. Review as page
content: Step 3 reads back everything you entered with "Change" links back to the
relevant step. This instance also persists to sessionStorage
(persist-key-value="apply-demo") — refresh the page after filling a field and your
answers (and current step) come back.
Page-level only: this pattern must never be hosted inside .popup —
popup_controller.close() isn't cancelable and Escape closes the topmost popup
immediately, which would destroy the wizard's data before a discard-confirm could open. The
discard-confirm itself is the one place this demo composes .popup.
Nothing has been sent to Hills Road Sixth Form College. If you discard, the answers you've entered will be deleted.
<div class="form-wizard steps" data-controller="steps form-wizard" data-form-wizard-persist-key-value="apply-demo" data-action="steps:beforeNext->form-wizard#handleBeforeNext steps:changed->form-wizard#handleStepsChanged steps:completed->form-wizard#handleCompleted submit->form-wizard#handleSubmit"><form novalidate data-form-wizard-target="form"><div class="form-wizard-steps steps-header">…</div><p class="form-wizard-progress" data-form-wizard-target="progress"></p><div class="steps-content"><div class="form-wizard-panel steps-panel active" data-steps-target="panel" data-form-wizard-target="panel"><h2 class="form-wizard-panel-title" tabindex="-1" data-form-wizard-target="panelTitle">…</h2>…</div>…</div><div class="form-wizard-footer steps-footer"><div class="form-wizard-footer-exit"><a class="btn btn-link" data-form-wizard-target="discardTrigger" data-action="click->form-wizard#requestDiscard">Cancel</a></div><button type="button" data-steps-target="backBtn" data-action="click->steps#back">Back</button><button type="button" data-form-wizard-target="submitBtn" data-action="click->steps#next">Continue</button></div></form><div data-form-wizard-role="discard-popup" data-controller="popup">…</div></div>
Timing: validation only runs on Continue/Submit — never on keystroke. Once a
field is marked invalid, it re-validates live on input/blur and clears itself (and its summary
row) the instant it passes. Each summary link's text is the identical string as that field's
.invalid-feedback — both are set from the same data-form-wizard-error
source, so the two can never drift apart.
2 answers need attention before you continue
As it appears on their birth certificate.
<div class="form-wizard-error-summary inline-notification inline-notification-error" role="alert" tabindex="-1"><div class="inline-notification-icon">…</div><div class="inline-notification-content"><p class="inline-notification-title">1 answer needs attention before you continue</p><div class="inline-notification-message"><ul class="form-wizard-error-summary-list"><li><a href="#field-id" class="form-wizard-error-summary-link">Enter your child's first name</a></li></ul></div></div></div> … <div class="invalid-feedback" id="field-id-error">Enter your child's first name</div>