Legend not such a legend anymore

by .

Lately I decided I was going to recreate the interactive features of the details element using JavaScript (apparently the same day as fellow Brightonian Jeremy Keith).

However I ran in to some very serious issues with the tag, so serious, in it’s current state, it’s unusable.

Overview of the details element

The details element, by default, is a collapsed element whose summary, or label, is the first child legend (if no legend is used, the UA provides a default, such as “Details”), with a triangular button to indicate it’s current open state.

If you include the open attribute, then the element is open by default. In theory, you could attach a click event to the legend, and switch the open attribute.

The markup would roughly be this:

<details open="open">
  <legend>Terms & Conditions</legend>
  <p>You agree to xyz, etc.</p>
</details>

Here’s the details test page I was working from: HTML 5 details test

The issues

The biggest problem, and the show stopper for me, is that the browser’s treatment of the legend element completely breaks this markup pattern – this is true for all the major browsers: Opera, Safari, Firefox and IE (tested in all the latest and some older browsers). I’ll go in these issues in detail in a moment.

Other problems include:

  • Styling the legend element is exceptionally difficult, particularly positioning
  • Using the WHATWG guidelines to styling the details element prove both difficult to interpret and difficult to implement.
  • When using CSS to style the open state of the details element using: details[open] { height: auto; }, meant that once I changed the open state using JavaScript, it wouldn’t trigger the browser to redraw (as it would if I had added a class). I’ve run in to this before, CSS 2.1 is styling source, not the DOM.

Legend treatment

Surprisingly Firefox is the worst one out in these issues, the rest of the browsers have fairly same treatment of the issue. In the screenshots, I’ve included a fieldset and nested legend for reference.

Internet Explorer

IE7 & IE8 closes the legend element it encounters when it’s not inside a fieldset element and move it’s contents out to an adjacent text node.

What’s also strange, is that looking at the DOM it also creates another empty(?) closed legend element after that text node. It doesn’t have any effect, but just looked odd:

IE's details element treatment

Opera

Opera (9 & 10b) is very similar to IE in it’s treatment of the legend in the details element, except it doesn’t create the second closing legend node. It just closes the legend, and creates the adjacent text node.

Safari

Safari simply strips the legend all together out of the DOM. So much so, that if you open the web inspector, then the error console, you’ll see it warning out that it’s encountered an illegal element, ignoring it, then encountering the closing tag, so it ignores that too. You’re left with just the text node.

Firefox

The best for last. Firefox goes one step beyond the other browsers. It assumes you’ve forgotten to include the fieldset element. So when it hits the legend element, Firefox inserts an opening fieldset up until it finds (I believe) the closing fieldset element, which obviously it doesn’t so the result is the rest of the DOM, after the first illegally placed legend ends up eaten by fieldset element, which leaves my DOM in a mess:

Firefox details treatment

Impact on other elements

details isn’t the only element that reuses the legend element for labelling, the figure element also is supposed to support the legend element. The result is obviously going to be the same.

Conclusion

We can’t style the legend element when the text is being thrown out by all the browsers, and Firefox’s DOM mangling is just too painful to look at.

This basically means that we can’t, in any reasonable amount of time, use the legend element inside both the details and figure element in the spec’s current state.

For me, I’ll be using an alternative element, probably just a p element styled to look like a legend, but that’s really not the point. Ideas anyone?

It turns out we weren’t the only ones looking at this and Ian Hickson has responded on the issue:

My plan here is to continue to wait for a while longer to see if the parsing issues can get ironed out (the HTML5 parser in Gecko for instance solves this problem for Firefox). If we really can’t get past this, we’ll have to introduce a new element, but I’m trying to avoid going there.

It’s fine to think that Gecko will update, but it’s IE that I’m worried about, they won’t turn out their render engine, and the result is we’ll have to avoid using the legend in any element other than fieldset.

19 Responses on the article “Legend not such a legend anymore”

bruce says

Oddly, Remington, I’d also been playing with <figure> for my OSCON and decided not to demo it as I couldn’t reliably style the legend that it uses to caption an image.

Here’s a test to style legend green, with nested small element blue

Warning: image of me in cocktail dress.

Jeremy Keith says

Thanks for writing this up, Remy. I was going to shoot off an email to the WHAT WG list to explain what I found when I tried the same experiments as you but this makes it a lot clearer where the problems lie.

Chris Mahon says

W3 seems to say that the correct markup for details is:

Your example has it open=open, I’m sure it won’t make any difference as legends have always been horrible to position but thought I’d point it out anyway.

Chris Mahon says

Also, your Firefox example is using the source generated by Firebug. Not sure how Firebug generates it’s HTML but it seems to insert all kinds of crap that isn’t actually in the page (check the actual source in Firefox, those Fieldsets are not there) so I’m not sure who is producing the correct HTML; firebug or view source in Firefox?

Lars Gunther says

I suppose Firefox will get this right in the next version using the HTML 5 parser Henri Sivonen is working on. But that might mean it won’t be usable until Firefox 3.5 has died out. Or that we have to send pages as XML to the Fox, as with FFox 2 and header, footer, aside, section, etc.

@Chris Mahon

There is a difference between GENERATED source and the original source. Firebug shows the former, aka the internal DOM representation of the document. The tool is not the problem. The HTML parser is.

Adam says

Can you provide a graphic of how it’s supposed to look, cause I don’t understand the use atm. triangular button?

AlastairC says

The legend element is a bit of a nightmare for current browser support. Even as a company that prides itself on accessibility, there have been several occasions we’ve dropped the use of legend in fieldsets in favour of headings.

Lars Gunther says

FWIW. I ran the test page in Minefield. The DOM is OK with HTML 5 parsing turned on.

It is also OK as expected in 3.5 using XHTML.

More confusing discrepancies: In FFox 3.5 document.querySelector and document.querySelectorAll tracks the DOM, not the source, in the few tests I just ran.

Legend Killer says

I’ve always hated legends and fieldsets. It just seems to go against the standard styling behaviour every other element obeys.

If an element 1 is inside div A and div A is set to overflow:hidden then any portion of element 1 that extends beyond the width/height of div A is not visible. Right?
But Legends are by default positioned to be halfway outside the top edge of the fieldset and when you make the fieldset overflow:hidden you can STILL SEE THE ENTIRE LEGEND! It does not obey! It’s so rude!

Remy Sharp says

@Chris Mahon – looks like your markup was stripped from the comment. None the less:

1. The open=”open” is valid, I suspect you had open=”true” or just ‘open’ as an attribute. It’s boolean, but not in the sense of it should be equal to true or false, it just needs to be present.

2. Firebug is showing me the rendered DOM which is what is being styled. So, the source code is one thing, the rendered DOM is the actual thing we’re interested in, and using Firebug, or the IE developer toolbar, we can see that the browser has changed the DOM to suit it’s parsing rules – hence this whole mess with the legend element.

@Lars Gunther – thanks for testing with the HTML 5 parsing turned on – and you’re right, it’s not going to be on by default until FF 3.5 has died out, ignoring IE for the minute – that’s going to be a long while! Which is what takes me back to using another element other than legend. Sending XHTML is fine, but not suitable for IE, and IE still and will continue to, have majority market – so we, as authors, need a solution that truly does pave the cowpaths.

Regarding the querySelector and querySelectorAll – I expect it to run against the rendered DOM, rather than the source. Keep in mind that this would then break if we modified the DOM, say changing the ID of an element, and ran querySelector – it should query the latest version of the DOM, and not the original source code that was delivered to the browser.

@Adam – the closest I was going by the description in the specs, but I’m no browser vendor, so I wouldn’t like to say! I’m assuming it looks like a normal p element with a triangle to the right that rotates when it’s open (again, this is going by the spec). Either way, without the triangle, it should appear, something like the normal legend + fieldset combo (I think!)

Andy says

Woah. That’s nuts. I’ve been using figure/legend quite extensively and hadn’t encountered this. I think it’s possibly because we aren’t using the document.createElement trick? Looking at our test pages in Firebug I just see the legend element has been disappeared from the DOM and it’s contents appears as a child of the figure.

Andy says

Ah, scratch what I said about the Javascript. Firefox doesn’t generate random fieldsets if the figure is contained within a block level element.

Aleksey says

I’m working on this issue right now, and I have a forward and backward compatible way to make this work, but unfortunately, it does require a little addition markup:

{details}
{legend}{span class=”legend”}Details{/span}{/legend}
{div}
{p}Detailed contents go here. They must be nested in a div unless I figure out a reliable way to move the original contents of the details element into a new div dynamically created. If you can help with that, I will be thankful; it’ll cut down on the extra markup required.{/p}
{/div}
{/details}

Once I am done with my solution, I will post a link to it here.

Aleksey says

I revamped my Fiks.html script and moved it to Google code. It includes the cross-browser solution for the details element and html 5 in general: http://code.google.com/p/fiks-html5/

Remy Sharp says

@Aleksey – as much as I hate to rain on your fire, but your solution doesn’t quite work :-( (it looks great by the way) – I released a similar demo last week: http://remysharp.com/demo/details-with-js.html (but I’m still not happy with it – the label element doesn’t have keyboard support in Safari, and Firefox still has general problems with keyboard focus).

Anyway, here’s where it doesn’t quite work: remove the wrapping div from the entire solution, and you’ll see Firefox trigger the vomit bug. Here’s a demo of that happening. Check out the markup delivered to Firefox, then check the rendered DOM (in Firebug), you’ll see they’re completely different. ;-(

Close, but interesting that the vomit bug doesn’t trigger if the details and legend element aren’t a direct decedent of the body element.

Aleksey says

I didn’t implement keyboard yet, but I think I can make it work correctly. I think we can put the ideas together and get something that at least somehow works. I don’t think we’ll have a choice when we start implementing this than to wrap details in a known block level element to be compatible with Firefox (new html 5 block elements don’t prevent the vmoit bug).

Also, there’s a problem with your solution with switching the display for the elements inside the details element which would make it have problems with elements that wouldn’t have a default display of block. The WHATWG recommendations specifies the default look for details like so:

details {binding: details; /* is a replaced element, thus technically has no style by default */}
details > _legend_wrapper {display: block; /* also has the arrow triangle thingy, but I won't write that here */}
details > _contents_wrapper {height: 0; overflow: hidden; /* everything inside details except the first legend element is to be wrapped in a virtual block (hence my div) which is then what has it's style changed when you expand/contract the details. The actual contents of the details stay untouched. */}
details[open] > _contents_wrapper {height: auto; overflow: auto;}

the DOM would then be:

details
..legend
..p the actual details

but what is getting styled would look like this:

details
..block_legend_wrapper (invisible to DOM)
….legend
..block_contents_wrapper (invisible to DOM)
….p the actual details

The legend is already a block element, so I just use that, but I emulate the contents wrapper with a div inside the details element which works, but is visible to the DOM of course.

Aleksey says

Sorry, small correction: legend isn’t a block element, but acts like one in most browsers when forced to be used inside a details or figure element.

Aleksey says

Updated my solution to include keyboard support. It works in Safari 4, but not Safari 3. Seems to work fine in Firefox too; what exactly goes wrong in Firefox with your’s?

Join the discussion.

Some HTML is ok

You can use these tags:
<a href="" title="">
<abbr title="">
<b>
<blockquote cite="">
<cite>
<del datetime="">
<em>
<i>
<q cite="">
<strong>

You can also use <code>, and remember to use &lt; and &gt; for brackets.