Adding A Form Inside Another Form In HTML

I recently shipped a new feature on Comics Outmash called “polls”. Each month or so, the admin can open a new “poll period” where people can suggest a title they’d like to read next. Once there has been enough suggestions, the admin flips a switch and the users can vote on the titles they’re interested in reading. Unsurprinsingly, the title with the most votes wins. As part of this feature, I went through a tiny rabbit hole of at least 4 internet searches and discovered that it’s not possible to add an HTML form inside another. Or, is it?

TL;DR: an input field doesn’t have to be a descendant of a form element if you use the form attribute. See this codepen for an example.

The Feature

While the poll is active, an user can update their suggestions as often as they want: maybe they read “Gideon Falls” by Jeff Lemire and wanted everybody to read it, but then read “Harley Quinn: Breaking Glass” by Mariko Tamaki and were super excited to talk about it with people. Heck, they might even decide to remove their suggestion altogether because they got more excited about other suggestions and wanted to give them a chance. As a result, I designed the feature like this:

A form for the authenticated user's suggestion, with a button to update the suggestion or one to delete it
A form for the authenticated user's suggestion, with a button to update the suggestion or one to delete it

The delete button wasn’t always here, but when I initially showed the feature to the users, we decided that it would make sense to add one.

The Problem With Forms

I already have a super basic component that I reuse in a bunch of places in this project. To delete a suggestion (or anything else anywhere in the project, especially in the admin console), I want users to submit a form and not just click on a link. It’s not enough on its own but it’s standard practice to minimize CSRF vulnerabilities.

Being the developer that I am - which means a developer who hasn’t written a lot of modern HTML in the past few years - I added my delete form inside my existing suggestion form, roughly like so:

<form>
    <input type="text" id="suggestion" value="Batman or whatever" />
    <button type="submit">Add suggestion</button>
    
    <form action="/suggestion/delete">
      <input type="hidden" name="item" value="1" />
      <button type="submit">Delete</button>
    </form>
</form>

In my head, it was a perfectly reasonable expectation that if you submit a form A that’s embedded inside a form B, it will submit the form A. I’m happy to report that it’s absolutely not the case. In fact, if you look at the inspector panel in your favourite browser, the second form is simply… not there:

Screenshot of the inspector panel showing that the form is not even rendered
Screenshot of the inspector panel showing that the form is not even rendered

Down the rabbit hole we go

I have to be honest, it’s more of a small burrow than a deep rabbit hole, but because I realized that my original assumption was incorrect, I decided to start searching, which brought me into the depth of Stack Overflow. Of course, I found a couple of weird hacks using javascript (which I wanted to avoid) but it didn’t take long to find a link1 to the official W3 specification that states the following:

Every form must be enclosed within a FORM element. There can be several forms in a single document, but the FORM element can’t be nested

Workaround

I got a little worried for a minute, because I didn’t want to have to rewrite all my form just so I could have a “delete” button next to my “submit” button. One of the solution I briefly considered was to add names to my buttons and process the form in a single endpoint. If the “save-suggestion” button was clicked, then I would create or update a suggestion but if the “delete-suggestion” button was pressed, then I would delete it. It’s dirty but it would have worked, even if that meant hating myself for a few days.

No, the actual solution is much, much simpler. Each form input, including buttons have a “form” attribute to specify which form they “belong” to. That’s right, your form elements do not need to be nested in said <form /> tag, as long as you set that form attribute, like so:

<form action="/suggestion/delete" id="delete-form">
    <input type="hidden" name="item" value="1" />
</form>

<form action="/suggestion" id="suggestion-form">
    <input type="text" id="suggestion" value="Batman or whatever" />
    <button type="submit">Add suggestion</button>

    <button type="submit" form="delete-form">Delete</button>
</form>

And voila! It just works, no javascript involved. I even made a codepen you can try, because apparently I’m that kind of person now.

Conclusion

The last decade or so of my career was spent working on (native) mobile apps and backend applications, writing APIs and wiring microservices together. I did write a fair bit of HTML but only for admin consoles, or as a result of getting overly excited by the developer experience provided by React and building Single Page Applications with a buttload of javascript. One of the reason I’ve enjoyed working on Comics Outmash is that I’m rediscovering how fun it is to make simple websites and learning fancy HTML techniques from StackOverflow answers posted in… 2009.

  1. Massive thanks to Marek Karbarz on this answer