HTML Fieldset Allows Children to Expand Indefinitely

HTML fieldset allows children to expand indefinitely

There appears to be no viable solution to this problem. If you really want a fieldset you can use a div and then style it to look like a fieldset but you'll run into tons of cross-browser issues and headaches. Best solution: refactor the form so that you're not using fieldsets anymore.

fieldset resizes wrong; appears to have unremovable `min-width: min-content`

Update (25 Sept 2017)

The Firefox bug described below is fixed as of Firefox 53 and the link to this answer has finally been removed from Bootstrap's documentation.

Also, my sincere apologies to the Mozilla contributors who had to block removing support for -moz-document partly due to this answer.

The fix

In WebKit and Firefox 53+, you just set min-width: 0; on the fieldset to override the default value of min-content

Still, Firefox is a bit… odd when it comes to fieldsets. To make this work in earlier versions, you must change the display property of the fieldset to one of the following values:

  • table-cell (recommended)
  • table-column
  • table-column-group
  • table-footer-group
  • table-header-group
  • table-row
  • table-row-group

Of these, I recommend table-cell. Both table-row and table-row-group prevent you from changing width, while table-column and table-column-group prevent you from changing height.

This will (somewhat reasonably) break rendering in IE. Since only Gecko needs this, you can justifiably use @-moz-document—one of Mozilla's proprietary CSS extensions—to hide it from other browsers:

@-moz-document url-prefix() {
fieldset {
display: table-cell;
}
}

(Here's a jsFiddle demo.)


That fixes things, but if you're anything like me your reaction was something like…

What.

There is a reason, but it's not pretty.

The default presentation of the fieldset element is absurd and essentially impossible to specify in CSS. Think about it: the fieldset's border disappears where it's overlapped by a legend element, but the background remains visible! There's no way to reproduce this with any other combination of elements.

To top it off, implementations are full of concessions to legacy behaviour. One such is that the minimum width of a fieldset is never less than the intrinsic width of its content. WebKit gives you a way to override this behaviour by specifying it in the default stylesheet, but Gecko² goes a step further and enforces it in the rendering engine.

However, internal table elements constitute a special frame type in Gecko. Dimensional constraints for elements with these display values set are calculated in a separate code path, entirely circumventing the enforced minimum width imposed on fieldsets.

Again—the bug for this has been fixed as of Firefox 53, so you do not need this hack if you are only targeting newer versions.

Is using @-moz-document safe?

For this one issue, yes. @-moz-document works as intended in all versions of Firefox up until 53, where this bug is fixed.

This is no accident. Due in part to this answer, the bug to limit @-moz-document to user/UA stylesheets was made dependent on the underlying fieldset bug being fixed first.

Beyond this, do not use @-moz-document to target Firefox in your CSS, other resources notwithstanding.³


¹ Value may be prefixed. According to one reader, this has no effect in Android 4.1.2 Stock Browser and possibly other old versions; I have not had time to verify this.

² All links to the Gecko source in this answer refer to the 5065fdc12408 changeset, committed 29ᵗʰ July 2013; you may wish to compare notes with the most recent revision from Mozilla Central.

³ See e.g. SO #953491: Targeting only Firefox with CSS and CSS Tricks: CSS hacks targeting Firefox for widely referenced articles on high-profile sites.

Does fieldset have to be in a form?

I think the question and chosen answer in this question are misleading. Whether or not a form has to have a fieldset, and whether or not a fieldset has to be in a form are two different questions with two different answers.

According to the HTML4.01 spec, a fieldset is a valid element inside of a form, but as it is a standard block-level element, it is also acceptable elsewhere:
http://www.w3.org/TR/html401/interact/forms.html#h-17.10
http://www.w3.org/TR/html401/sgml/dtd.html#block

I can not, however, imagine a use case where this would be done, unless you are using the fieldset for decorating, which would be incorrect usage.

A form however, does not require a fieldset:
http://www.w3.org/TR/html401/interact/forms.html#h-17.3

last-of-type selector not working as expected

Instead of <div>s and <label>s use <fieldset>s and <legend>s because:

  • It makes your <form> semantically perfect.
  • It looks better
  • Using CSS as the main means of finding elements in the DOM is very limiting, so having <div>s and <span>s everywhere will lessen your chances at getting specific selectors.

Now your layout is semantic, and using nth-of-type is a no brainer:

Demo

fieldset:last-of-type {
margin-bottom: 50px;
border-color: red
}
<form role="form" method="POST" action="{{ route('login') }}">
{{ csrf_field() }}
<fieldset class="form-input">
<legend>Username</legend>
<input id="email" placeholder="Enter your email" type="email" name="email" value="{{ old('email') }}" required autofocus>
</fieldset>
<fieldset class="form-input">
<legend>Password</legend>
<input id="password" placeholder="Enter your password" type="password" name="password" required>
</fieldset>
<fieldset class="form-input">
<legend>Username</legend>
<input id="email" placeholder="Enter your email" type="email" name="email" value="{{ old('email') }}" required autofocus>
</fieldset>
<fieldset class="form-input">
<legend>Password</legend>
<input id="password" placeholder="Enter your password" type="password" name="password" required>
</fieldset>
<fieldset class="form-input">
<legend>Username</legend>
<input id="email" placeholder="Enter your email" type="email" name="email" value="{{ old('email') }}" required autofocus>
</fieldset>
<fieldset class="form-input">
<legend>Password</legend>
<input id="password" placeholder="Enter your password" type="password" name="password" required>
</fieldset>
<fieldset class="form-input">
<legend>Username</legend>
<input id="email" placeholder="Enter your email" type="email" name="email" value="{{ old('email') }}" required autofocus>
</fieldset>
<fieldset class="form-input">
<legend>Password</legend>
<input id="password" placeholder="Enter your password" type="password" name="password" required>
</fieldset>
<fieldset class="form-input">
<legend>Username</legend>
<input id="email" placeholder="Enter your email" type="email" name="email" value="{{ old('email') }}" required autofocus>
</fieldset>
<fieldset class="form-input">
<legend>Password</legend>
<input id="password" placeholder="Enter your password" type="password" name="password" required>
</fieldset>
<fieldset class="form-input">
<legend>Username</legend>
<input id="email" placeholder="Enter your email" type="email" name="email" value="{{ old('email') }}" required autofocus>
</fieldset>
<fieldset class="form-input">
<legend>Password</legend>
<input id="password" placeholder="Enter your password" type="password" name="password" required>
</fieldset>

<div class="buttonWrapper">
<button type="submit">Login</button>
</div>
<div style="padding-bottom: 20px;" class="text-center">
<a class="text-uppercase text-condensed" href="">
Forgot Your Password?
</a>
</div>
</form>

Unclosed / misnested HTML tags extend past their parent

Thanks to @Ankith Amtange I found the explanation of what happens. I'll write it out here for future readers.

The <s> tag extends past its parent because it is a formatting element. The <sup> tag is automatically closed because the browser expected a closing </sup> tag before the end of the parent element.

The HTML parser treats elements differently in its stack, which fall into the following categories (source):

Special elements

  • The following elements have varying levels of special parsing rules: HTML's address, applet, area, article, aside, base, basefont, bgsound, blockquote, body, br, button, caption, center, col, colgroup, dd, details, dir, div, dl, dt, embed, fieldset, figcaption, figure, footer, form, frame, frameset, h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, html, iframe, img, input, isindex, li, link, listing, main, marquee, meta, nav, noembed, noframes, noscript, object, ol, p, param, plaintext, pre, script, section, select, source, style, summary, table, tbody, td, template, textarea, tfoot, th, thead, title, tr, track, ul, wbr, and xmp; MathML's mi, mo, mn, ms, mtext, and annotation-xml; and SVG's foreignObject, desc, and title.

Formatting elements

  • The following HTML elements are those that end up in the list of active formatting elements: a, b, big, code, em, font, i, nobr, s, small, strike, strong, tt, and u.

Ordinary elements

  • All other elements found while parsing an HTML document.

Explanation (from linked spec):

The most-often discussed example of erroneous markup is as follows:

<p>1<b>2<i>3</b>4</i>5</p>

The parsing of this markup is straightforward up to the "3". At this point, the DOM looks like this:

─html
├──head
└──body
└──p
├──"1"
└──b
├──"2"
└──i
└──"3"

Here, the stack of open elements has five elements on it: html, body, p, b, and i. The list of active formatting elements just has two: b and i. The insertion mode is "in body".

Upon receiving the end tag token with the tag name "b", the "adoption agency algorithm" is invoked. This is a simple case, in that the formatting element is the b element, and there is no furthest block. Thus, the stack of open elements ends up with just three elements: html, body, and p, while the list of active formatting elements has just one: i. The DOM tree is unmodified at this point.

The next token is a character ("4"), triggers the reconstruction of the active formatting elements, in this case just the i element. A new i element is thus created for the "4" Text node. After the end tag token for the "i" is also received, and the "5" Text node is inserted, the DOM looks as follows:

─html
├──head
└──body
└──p
├──"1"
├──b
│ ├──"2"
│ └──i
│ └──"3"
├──i
│ └──"4"
└──"5"


Related Topics



Leave a reply



Submit