Skip to content

Stop nesting form elements in the checkout address step - #1099

Draft
boo-code wants to merge 1 commit into
PrestaShop:2.xfrom
boo-code:fix/checkout-address-form-nesting-36563
Draft

Stop nesting form elements in the checkout address step#1099
boo-code wants to merge 1 commit into
PrestaShop:2.xfrom
boo-code:fix/checkout-address-form-nesting-36563

Conversation

@boo-code

@boo-code boo-code commented Sep 5, 2026

Copy link
Copy Markdown
Contributor
Questions Answers
Description? The checkout address step opens a <form> around the whole block and then renders full address <form> elements inside it. HTML forbids nesting, and the parser resolves it by ignoring the inner start tag and letting the inner </form> close the OUTER form - so every control rendered after an address form loses its form owner. This attaches the step's own controls (address selector radios, Continue button, not-valid-addresses) to the step form through the form attribute and leaves that form element empty so the address forms are siblings rather than children. initFormValidation looked its submit button up with querySelector, which only sees DOM descendants, so it now searches form.elements, which covers both containment and the form attribute.
Type? bug fix
BC breaks? no
Deprecations? no
Fixed ticket? PrestaShop/PrestaShop#36563
Sponsor company -
How to test? On a 9.2 shop with this theme active: reach checkout with two saved addresses, click "Billing address differs from shipping address", then "Edit" on the delivery address. Before this change the Continue button, both invoice radios and #not-valid-addresses have no form owner (document.querySelector('button[name=confirm-addresses]').form is null), so Continue does nothing. After, all three belong to the step form.

Measured on 9.2.0 with this theme active, counting <form> tags in the address step of the served HTML and
reading form ownership from the parsed DOM:

state                                       before         after
guest, 0 addresses, delivery form       4 tags depth 2       -
1 address,  editAddress=delivery        6 tags depth 2   6 tags depth 1
2 addresses, editAddress=delivery       4 tags depth 2   4 tags depth 1
selectors only                                  -        2 tags depth 1
newAddress=delivery                             -        4 tags depth 1

Form ownership in the two-address state, before -> after:

button[name=confirm-addresses]   null -> the step form
input[name=id_address_invoice]   null -> the step form   (both radios)
#not-valid-addresses             null -> the step form

Tests

src/js/form-validation.test.ts - 3 cases: a submit button nested in the form, a submit button attached
through the form attribute, and a form with no submit button. Reverting only src/js/form-validation.ts
fails exactly the second one. Full jest suite 42/42, eslint clean.

Why the form attribute rather than moving the markup

The step form is needed for the address selectors and the Continue button; the address form is needed for
the address fields. They are never the same submission, but they interleave: a delivery selector can be
followed by an invoice form, so no arrangement of open/close tags puts every selector in the same form as
the Continue button. Explicit form ownership is the platform feature for exactly that, and it keeps source
order, markup and CSS unchanged.

Companion

PrestaShop/classic-theme needs the same change (branch fix/checkout-address-form-nesting-36563, base
develop), plus replacing the extra <form> its checkout/_partials/address-form.tpl opens around the
Continue button with a <div> - hummingbird already does that.

The address step wrapped the whole block in a <form>, and rendered full address
<form> elements inside it. The HTML parser resolves that by dropping the inner
start tag and letting the inner </form> close the outer form, so every control
after the address form loses its form owner. Measured on 9.2.0: with two saved
addresses, a separate invoice address and the delivery address open for editing,
the Continue button, both invoice radios and the not-valid-addresses input all
report form === null.

Attach those controls to the step form through the form attribute instead, and
leave the form element itself empty so the address forms stay siblings. Form
validation now looks the submit button up in form.elements, which covers both
DOM containment and the form attribute.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant