November 12th, 2008Nesting FORM tags in (X)HTML
Due to ASP.NET’s requirements with regards to server side controls being in one big FORM tag, I started looking at nesting FORM tags in one (X)HTML document.
Formalities
First, let’s have a look at the official documents.
HTML 4 specification says
A form can contain text and markup (paragraphs, lists, etc.) in addition to form controls.
DTD says that FORM element can contain aby block element apart from another FORM
<!ENTITY % block
"P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT | BLOCKQUOTE | FORM | HR | TABLE | FIELDSET | ADDRESS">
<!ELEMENT FORM - - (%block;|SCRIPT)+ -(FORM) -- interactive form -->
However, following the nesting path, there should be no problem in using some block element and nesting another FORM within that. Let’s take DIV for example.
<!ELEMENT DIV - - (%flow;)* -- generic language/style container -->
<!ENTITY % flow "%block; | %inline;">
We can see that DTD officially forbids only nesting FORM directly within another FORM. It doesn’t say anything about nesting FORMs within block elements, within other FORMs, or even deeper within the DOM tree.
But let’s see how does it work in the real world.
Experiments
Let’s try this practically. We’ll use the standard W3 validator and Firefox DOM inspector to examine site content at each step.
Attempt 1 - the obvious
First, we’ll use a simple XHTML document with one FORM tag.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Test doc</title>
</head>
<body>
<form action="#" method="post" id="container-form">
<fieldset>
<input type="text" id="container-input" name="container-input"/>
<input type="submit" id="container-submit" value="Submit container form"/>
</fieldset>
</form>
</body>
</html>
Of course, validator says it’s a correct document.

Within the DOM tree, we can see our FORM successfully.

Attempt 2 - wrong
Now, let’s add another FORM within the first one
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Test doc</title>
</head>
<body>
<form action="#" method="post" id="container-form">
<fieldset>
<input type="text" id="container-input" name="container-input"/>
<input type="submit" id="container-submit" value="Submit container form"/>
</fieldset>
<form action="#" method="post" id="inner-form-1">
<fieldset>
<input type="text" id="inner1-input" name="inner1-input"/>
<input type="submit" id="inner1-submit" value="Submit inner form 1"/>
</fieldset>
</form>
</form>
</body>
</html>
As expected, validator says it’s incorrect - true, we cannot nest FORM directly within a FORM.

If we look at the DOM tree in a browser, we see how the browser is trying to glue both FORMs together

Attempt 3 - theoretically correct
Now, let’s put the inner FORM within a block level container. This markup is correct according to specification and DTD.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Test doc</title>
</head>
<body>
<form action="#" method="post" id="container-form">
<fieldset>
<input type="text" id="container-input" name="container-input"/>
<input type="submit" id="container-submit" value="Submit container form"/>
<form action="#" method="post" id="inner-form-1">
<fieldset>
<input type="text" id="inner1-input" name="inner1-input"/>
<input type="submit" id="inner1-submit" value="Submit inner form 1"/>
</fieldset>
</form>
</fieldset>
</form>
</body>
</html>
Validator indeed says the document is OK.

Let’s try the DOM inspector.

What happens? The browser is still gluing the two forms together, although the markup is correct. This is something, that should not happen. Let’s experiment a bit more
Attempt 4 - the quirks begin
Now, let’s add some more properly marked up nested forms and see what happens.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Test doc</title>
</head>
<body>
<form action="#" method="post" id="container-form">
<fieldset>
<input type="text" id="container-input" name="container-input"/>
<input type="submit" id="container-submit" value="Submit container form"/>
<form action="#" method="post" id="inner-form-1">
<fieldset>
<input type="text" id="inner1-input" name="inner1-input"/>
<input type="submit" id="inner1-submit" value="Submit inner form 1"/>
</fieldset>
</form>
<form action="#" method="post" id="inner-form-2">
<fieldset>
<input type="text" id="inner2-input" name="inner2-input"/>
<input type="submit" id="inner2-submit" value="Submit inner form 2"/>
</fieldset>
</form>
<form action="#" method="post" id="inner-form-3">
<fieldset>
<input type="text" id="inner3-input" name="inner3-input"/>
<input type="submit" id="inner3-submit" value="Submit inner form 3"/>
</fieldset>
</form>
<form action="#" method="post" id="inner-form-4">
<fieldset>
<input type="text" id="inner4-input" name="inner4-input"/>
<input type="submit" id="inner4-submit" value="Submit inner form 4"/>
</fieldset>
</form>
</fieldset>
</form>
</body>
</html>
Again - validator says it’s fine.

Let’s see what DOM insepctor tells us this time.
.
Now we can see some weird behaviour here. The browser is gluing the first inner form, but processes the other forms properly
Conclusions
Despite the fact that nesting forms is theoretically possible, browsers do not render it properly. A workaround needs to be used. On the other hand, specifications can be updated to clearly prohibit nesting FORM tags.
Workaround
The basic workaround is to nest additional, unnecessary form, just for this bug’s sake. Say, for 1 main and 2 nested forms, you would create such document then
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Test doc</title>
</head>
<body>
<form action="#" method="post" id="container-form">
<fieldset>
<input type="text" id="container-input" name="container-input"/>
<input type="submit" id="container-submit" value="Submit container form"/>
<form action="#" method="post" id="nested-form-bug"><div/></form>
<form action="#" method="post" id="inner-form-1">
<fieldset>
<input type="text" id="inner1-input" name="inner1-input"/>
<input type="submit" id="inner1-submit" value="Submit inner form 1"/>
</fieldset>
</form>
<form action="#" method="post" id="inner-form-2">
<fieldset>
<input type="text" id="inner2-input" name="inner2-input"/>
<input type="submit" id="inner2-submit" value="Submit inner form 2"/>
</fieldset>
</form>
</fieldset>
</form>
</body>
</html>
Then you just have to hide the dummy form
#nested-form-bug {
position: absolute !important;
left: -2500px !important;
width: 20px !important;
}
Validator still treats is as valid document.

In the DOM tree, we get what we want - 3 forms, 2 nested into the container form.

To do
- A validator bug is already reported.
- Check if it’s browser-consistent
- Check what browsers are sending to server for such nested forms