Reset your fieldset
Starting off the new year with a blast from the past – wrestling with the very stubborn <fieldset>
is sure to get your juices flowing!
(This feels like the good old days. Blogging about browser inconsistencies and CSS tricks to counter them. I think the need for that kind of knowledge hasn’t gone away, it has mostly become unfashionable. Sure, browsers have gotten more consistent, but there’s also more browsers. Anyway.)
Ah, the fieldset
! The semantically correct way to group sets of form fields, with a legend
element to act as heading. The legend is also often read by screen readers as a prefix to a field label. For example, in a form to fill in multiple passengers, screen readers would announce"Passenger 2 - name" instead of just "name", if the "name"-field was inside a fieldset labeled "Passenger 2". So if you have several fields where the label might be hard to tell apart, fieldsets and legends help.
Yet, I rarely see fieldsets in the wild. I think a lot of people know about them, they just don’t use them because they are damn near impossible to style.
Fieldsets have a weird default look: a border around the fieldset (so far so good) and then the legend sitting perched on the top border (wha..?), a little bit indented and with the border behind it clipped away. This placement would have to be done with positioning, which would be somewhat weird and unexpected in the UA stylesheet. Also, the disappearing border behind the legend is kind of impossible to do in a sane way in there.
So a fieldset’s default rendering doesn’t really seem to be controlled entirely by the browser default CSS. Each engine seems to have their own hardcoded little hell of exceptions. I can imagine there are a lot of cursing in the comments of the layout engine source code.
It seems to my (layman) eye that the top edge of the painted rectangle comprising the fieldset is shrunk to go behind the legend. Most browsers seem to shrink both the border-box and the padding-box (so to speak), but surprise, IE 8 is weird and only shrinks the border, not the padding-box, so the background sticks out outside the border.
For reasons like this, I stick to trying to remove styling from the fieldset instead of actually styling it. The idea is to remove the effects of all the hardcoded stuff, until the fieldset is just an invisible semantic wrapper, and the legend becomes just another block-level text element, situated at the top (normally top left) of the containing wrapper. We can then style a wrapper element around the fieldset instead, and style the legend (carefully).
Fieldsets and legends have a bit of default styling that is set with CSS. There’s some default margin and padding that we can remove, as well as the border. There are some further inconsistencies though.
- Legend elements are prevented from line-wrapping in IE, thus sticking out of narrow fieldsets.
- Both WebKit/Blink and Firefox have a minimum width based on the content inside the fieldset. Also causes sticking-outy-stuff of narrow fieldsets (like on mobile screens). More on that in a bit.
- Margins don’t seem to have the expected effect on legend elements.
- Margins on elements following the legend may affect the rendered margin of the legend.
The first point is quite easy to address — Roger wrote about this back in 2012. (I feel like I’m somehow feebly attempting to channel his heroic writing about these kinds of inconsistencies by the way. The man’s a legend.)
All you need to do is change the default display mode of the legend element to table
:
legend {
display: table;
}
The second point concerning minimum width of fieldsets is a bit weirder. Judging from the comments on this bug report (a Firefox bug open since 2009, by the way), it’s there for legacy reasons. It takes its value from the minimum intrinsic width of the contents of the fieldset. I noticed it because fields were sticking out of their container on mobile viewports when using a fieldset. I had the fields inside the fieldset at 100% width
, and at some point, both Safari, Chrome and Firefox went "Nope, that’s too narrow, the default width of the text field takes over", and let the fieldset stick out of its wrapper.
In WebKit/Blink, this is fairly easy to counter, since it’s part of the UA style sheet (as min-width: -webkit-min-content
). Taken together with the other reset styles, the fieldset reset style looks like this:
fieldset {
border: 0;
padding: 0;
margin: 0;
min-width: 0;
}
Firefox, however, decided that this width is waaaay to important to let developers touch. It’s just there — boom, no UA stylesheet rule for you! The fieldset is at least as wide as the contents, and if the contents depend on the width of the fieldset? You’re out of luck. Almost.
The fix is to set the display mode of the fieldset to table-cell
or table-column
(various table-related display modes seem to work). Unfortunately, that messes with IE, which generally gets a bit pissy about table-rendering, it seems. So we need to target only Firefox. Turning to Browserhacks, we’ll use a selector that is only understood by any (reasonably not-antique) version of Firefox:
body:not(:-moz-handler-blocked) fieldset {
display: table-cell;
}
Can you feel the tiny bit of puke in the back of your throat?
Anyway, onwards. Numbers 3 and 4 seem to have to do with some weird margin-collapsing behavior that is coupled with the rendering of legend elements. I’m guessing it is some special casing to do with their perched-atop-the-top-border thing. I’m not sure exactly what’s going on, but the general consensus seems to be "don’t mess with the margin of legend elements!". Padding will have to do. But issue number 4 can still bite you! This one is a bit tricky.
Because the label element has some demented margin-collapsing going on, any top margin you put on elements directly following the legend in the source code will be snuck up above the legend in WebKit/Safari, making it jump down.
According to this StackOverflow thread, you can mess with this using -webkit-margin-top-collapse: separate
on the direct sibling of the legend, but I had no such luck. Instead, I found that this special case disappeared as soon as there was any padding at all on the fieldset (yes, the fieldset, not the legend). We can use a fraction of a pixel for this, so the updated fieldset reset looks like this:
fieldset {
border: 0;
padding: 0.01em 0 0 0;
margin: 0;
min-width: 0;
}
And that’s it. At least as far as the reset goes. It seems slightly ridiculous how much magic is involved, still, in browsers rendering fieldsets. It’s not the most common of elements, but if we are to use it sensibly at all, we have to be able to style it, right?
Update: here’s the final reset, for all you lazy copypasteing bastards (looking at you, @hjalle).
legend {
padding: 0;
display: table;
}
fieldset {
border: 0;
padding: 0.01em 0 0 0;
margin: 0;
min-width: 0;
}
body:not(:-moz-handler-blocked) fieldset {
display: table-cell;
}