Tricking Browsers into Nesting Forms

Turns out the HTML parser and DOM APIs don’t always agree on semantic constraints.

HTML does not permit nesting <form> elements:

<form method="dialog">
    <button>Submit</button>
    <dialog>
        <form method="dialog">
            <button>Close</button>
        </form>
    </dialog>
</form>

Here the HTML parser discards the nested <form> element, leaving its <button> as a direct descendant of <dialog>.

However, the same structure works if we insert our nested form via the DOM instead:

<form method="dialog">
    <button>Submit</button>
    <dialog></dialog>
</form>
let form = document.createElement("form");
form.setAttribute("method", "dialog");

let btn = document.createElement("button");
btn.textContent = "Close";
form.appendChild(btn);

document.querySelector("form dialog").appendChild(form);

Brian Kardell points out that this is expected behavior:

it’s [possible] to create trees that are impossible to create with the parser itself, if you do it dynamically. With the DOM API, you can create whatever wild constructs you want

In fact, that nested form appears to behave just like a regular one: It properly closes the dialog without any custom JavaScript. (There’s a custom submit event handler in both demos above to open the respective dialog.)

Note that all this appears to be independent of <dialog>, i.e. it works the same way with a <div>.

Either way, this is probably not something we should rely on. There are other options these days.