Summary

Internet Explorer 6 has very poor support for in-page navigation. In-page navigation is an important accessibility requirement for several groups, including those with some types of mobility problems. This article investigates the problem with in-page links and Internet Explorer, with some possible solutions.

Author: Gez Lemon

Contents

Internet Explorer's hasLayout Property

Jim Thatcher recently mentioned the problem of in-page links not working correctly from the keyboard in Internet Explorer 6. The problem Jim is referring to is that when using the keyboard to navigate to an in-page link, pressing the tab key once the target has received focus causes IE to continue navigating from the start of the document (or more accurately, the closest ancestor with the hasLayout property set to true) in IE6. In his article, Jim makes the following observation:

IE6 places input focus on the first ancestor object of the target for which the property hasLayout has the value true.

The hasLayout property is a proprietary Microsoft property that is set automatically for body, img, input, table and td elements. Microsoft also provides a table outlining CSS properties that cause an element's hasLayout property to be set to true.

CSS Properties
CSS Property Value
display inline-block
height any value (block-level elements only)
float left or right
position absolute
width any value (block-level elements only)
writingMode (Microsoft proprietary property) tb-rl
zoom (Microsoft proprietary property) any value

Microsoft also mention that setting the contentEditable property to true will also cause an element's hasLayout property to be set to true.

Based on his observations, Jim uses the following markup to ensure in-page links work correctly with IE6:

<div style="width:0%;height:0px;">
<a name="maincontent" id="maincontent">
<img src="/images/1-pix.gif" width="0" height="0" alt="" />
</a>
</div>

Explicit tabindex Value

Looking into the problem further, I discovered that if an element is given an explicit tabindex value, keyboard navigation works correctly. The tabindex attribute is a valid attribute for the a, area, button, input, object, select, and textarea elements. A tabindex value of "0" means that elements are navigated in the order they are found in the document. Anchors with an href attribute are automatically given an implicit tabindex value of "0", so keyboard navigation for named anchors with an href attribute also work as expected. Providing a tabindex value of "0" solves the problem of in-page links where the target is a named anchor without an href attribute:

<p>
<a name="targetname" tabindex="0"></a>
Content ...
</p>

The problem with this technique is that a valid tabindex value automatically puts the element into IE's tab order. That means that when tabbing through a document, visitors using IE will find that focus is given to elements that they wouldn't usually expect to receive focus. Providing a negative tabindex value solves this problem:

<p>
<a name="targetname" tabindex="-1"></a>
Content ...
</p>

Validity and Keyboard Navigation

The technique outlined above ensures that keyboard navigation works as expected with IE, but introduces problems with validity. The tabindex attribute expects values between 0 and 32767, and negative numbers are illegal. For some reason, both the W3C's markup validator and the Web Design Group's markup validator complain about a negative tabindex value for HTML, but appear to allow them for XHTML. XHTML is a reformulation of HTML 4.01, and I can't see a reason for allowing negative tabindex values in the XHTML specification. If this is obvious, please leave a comment explaining why.

There is also a problem using the name attribute with XHTML 1.0. Older user agents do not support fragment identifiers that point to an id attribute. Rather than make the name attribute for a, form, img, and map obsolete, XHTML 1.0 formerly deprecates the name attribute for these elements, and the name attribute will be removed from subsequent versions of XHTML, as it is in XHTML 1.1.

I'm not so concerned with backwards compatibility, but I do care that content is accessible. I don't use named anchors, and prefer to use the id attribute as a target for fragment identifiers for both HTML and XHTML. I almost exclusively use h2 elements as the target for fragment identifiers. For example, in this document, the fragment identifier #toc points to the Contents h2 header element. Providing a tabindex value of "-1" on all h2 elements ensures that keyboard navigation works as expected with IE, but doesn't put the header elements into the tab order. As it's only IE that has problems with keyboard navigation with in-page links, and I don't want to add irrelevant markup for other user-agents, I've added the tabindex value to h2 elements through scripting in a conditional comment that just targets Internet Explorer:

function addIETabOrder()
{
  var objHeadings = document.getElementsByTagName('h2');

  for (var iCount=0; iCount<objHeadings.length; iCount++)
    objHeadings[iCount].tabIndex = -1;
}

This approach ensures that keyboard navigation works as expected in IE, but introduces even more problems with validity. Not only are negative values invalid for the tabindex attribute, the tabindex attribute is not valid for header elements. Despite my stance on validity being important for accessibility, this is one of those situations where I believe an invalid attribute with an invalid value would be excusable as there's an obvious benefit for accessibility, providing it's documented somewhere. In my case, the use of an invalid attribute is documented in this article. The Web Hypertext Application Technology Working Group (WHATWG) recognise the need for negative tabindex values, and is also acknowledged by Mozilla.

The problem doesn't end there; keyboard navigation is essential for people with some types of mobility problems, but also important for people using assistive technology as they also heavily rely on keyboard navigation. Whilst some assistive technologies partially support scripting, they don't tend to understand changes made to the DOM once the document has loaded. The technique I've used will benefit people with mobility problems relying on keyboard navigation, but may not help people using certain assistive technologies that use IE as a basis for the DOM.

Category: Accessibility.

Comments

  1. [ie-keyboard-navigation.php#comment1]

    The reason the validator complains about negative tabindex values for HTML 4, but not XHTML 1 can be found by looking at the DTDs.

    In HTML 4, the tabindex attribute is defined as:

    tabindex NUMBER #IMPLIED -- position in tabbing order --

    In XHTML 1, it is defined as:

    tabindex %Number; #IMPLIED
    <!ENTITY % Number "CDATA">

    In SGML, NUMBER means one or more digits from 0-9. CDATA means any character data. So, tabindex="foo" would pass validation in XHTML, but that doesn't make it conformant. Neither are negative numbers.

    Posted by Lachlan Hunt on

  2. [ie-keyboard-navigation.php#comment2]

    Hi Lachlan,

    In SGML, NUMBER means one or more digits from 0-9. CDATA means any character data. So, tabindex="foo" would pass validation in XHTML, but that doesn't make it conformant. Neither are negative numbers.

    Excellent! Thank you for the explanation; that makes perfect sense. I wondered why the validators didn't pick it up, but knew it had to be invalid.

    Cheers,

    Posted by Gez on

  3. [ie-keyboard-navigation.php#comment3]

    any value (block-level elements only)

    I don't see why this would be restricted to block-level elements.

    Another work around is to trigger hasLayout in your IE stylesheet:

    
    /* trigger hasLayout to fix IE keyboard nav bug */
    h2 { zoom:1; }
    

    Posted by zcorpan on

  4. [ie-keyboard-navigation.php#comment4]

    Hi ZCorpan,

    I don't see why this would be restricted to block-level elements.

    The width and height properties are ignored for inline element by IE in strict standard compliance mode, but would be applied in quirksmode.

    Another work around is to trigger hasLayout in your IE stylesheet:

    Yes, but a redundant container would be required containing just the h2 element, as keyboard navigation continues from the nearest ancestor where the hasLayout property is true, not the actual element.

    The key to discovering tabindex made a difference was trying the contentEditable property directly on the element. That caused IE's keyboard navigation to behave correctly, but added the element to IE's tab order.

    Posted by Gez on

  5. [ie-keyboard-navigation.php#comment5]


    For inline elements, width and height trigger hasLayout in IE 5.x and IE 6 in quirksmode only.

    When triggered on inline level elements (i.e. via zoom:1; in IE5.5+), "haslayout" will make them behave more like inline-block.

    http://www.satzansatz.de/cssd/onhavinglayout.html#inline

    The Keyboard Navigation Bug in IE/Win and its connection to the haslayout concept is a very interesting finding, thanks.

    Posted by Ingo Chao on

  6. [ie-keyboard-navigation.php#comment7]

    The following markup seems to take care of the bug, but as Jim Thatcher pointed out, it creates a problem with screen-readers.

    
    <a name="zTop" id="zTop" href="#"></a>
    

    Posted by Thierry on

  7. [ie-keyboard-navigation.php#comment8]

    It should be noted that the conditions for triggering the bug are quite specific. Typical methods of laying out content using CSS will often 'fix' the problem. For example, if the skip nav link is in a separate div, and the content div (containing the destination anchor) has an explicit width, the bug will not be triggered.

    There are also some additional benefits to the alternative solution of wrapping anchors around headings and setting the anchor href value to the fragment identifier, e.g:

    
    <h3>
    <a name="dest" id="dest" href="#dest">Destination heading</a>
    </h3>
    

    This enables other users to create a link or bookmark part of the content. Unwanted underlines, etc. can be removed using CSS.

    See http://www.motive.co.nz/glossary/anchor.php for more info.

    Posted by Andy Kirkwood on

  8. [ie-keyboard-navigation.php#comment9]

    Slightly unrelated, but IE is not the only bird suffering from problems with keyboard navigation. Safari equally has problems with this. That browser never shifts the focus. On this very page, if correctly follows the link to an in-page anchor, but the next tab does not jump to the next link, the page jumps back to the top, and the tab goes to next link after the one that was focussed.

    Posted by Philippe on

  9. [ie-keyboard-navigation.php#comment10]

    Nice fix... saves having to pollute the html source with extra spans and divs.

    I'm wondering if the tabIndex script above could be reformulated as a css expression, which, reportedly, is evaluated regardless of the users js settings?

    Posted by Terrence Wood on

  10. [ie-keyboard-navigation.php#comment11]

    yes it can and it works with js disabled.

    
    h2{
    behavior:expression((this.runtimeStyle.behavior="none")&&(this.tabIndex="-1"));
    }
    
    a{
    behavior:expression((this.runtimeStyle.behavior="none")&&(this.tabIndex="0"));
    }
    

    Since none of the above validates make sure you use conditional comments to delvier the styles to IE6.

    Thanks to Dean Edwards whose data url fix is the inspiration for the above code.

    Posted by Terrence Wood on

  11. [ie-keyboard-navigation.php#comment12]

    Thank you Thierry and Andy for some good solutions. I suppose it comes down to whether you want to add extra markup for IE, and add elements to a user-agent's tab order. There's also the issue of whether you want to use the name attribute with XHTML, as it's formerly deprecated, although obviously valid for the moment.

    Browser support is particularly bad for keyboard navigation, Philippe. Before the fix, only Firefox successfully implemented keyboard navigation on this website. Even Opera, which has very good keyboard support, seems to get confused with in-page links where the target is before the current position in the document, although successfully navigates forwards through the document.

    Adding the behaviour as an expression for IE is a great idea, Terrence. It doesn't work for me with scripting switched off, though. When you disabled scripting, did you test on a site that's in your trusted websites?

    Posted by Gez on

  12. [ie-keyboard-navigation.php#comment13]

    This characterization of the IE6 keyboard bug was discovered by Terrance Wood, not by me.

    IE6 places input focus on the first ancestor object of the target for which the property hasLayout has the value true.

    Even though my workaround for this keyboard handling IE6 bug is still secure as is, I assume, the one proposed here by Gez, the mystery seems still to be open.

    If the characterization of the IE6 input focus bug quoted above was correct then the third link of the GAWDS home page ("Jump to Diary Archive") should have worked correctly but it didn't. But now that example is gone, as it appears that someone has fixed that link with a different work-around. I think I will pretend I never saw it.

    Posted by Jim Thatcher on

  13. [ie-keyboard-navigation.php#comment14]

    To avoid polluting the markup (using the href attribute fix), one can use a CSS expression inside a Conditional Comment (for the same reason Terrence Wood suggested):

    
    #zTop {behavior:expression(this.href="#")}
    

    minimum markup:

    
    <a name="zTop" id="zTop"></a>
    

    Posted by Thierry on

  14. [ie-keyboard-navigation.php#comment15]

    Nice work, Thierry. A class may be a better choice, as it wouldn't require defining a new rule for each named anchor, but a good solution.

    Posted by Gez on

  15. [ie-keyboard-navigation.php#comment17]

    Yes, the site was in my trusted zone. It may be becuase I was tested on an older version of XP (i.e. not SP2).

    Yet another refinement, to get named anchors only - no classes or id's required:

    
    a{
    behavior:expression((this.runtimeStyle.behavior="none")&&(if(this.name) this.href='#'))
    }
    

    The first part of the expression turns off the behavior after it has run once, I think that's important YMMV.

    Posted by Terrence Wood on

  16. [ie-keyboard-navigation.php#comment18]

    That's a very elegant solution and I believe it would be *the* perfect solution if it didn't rely on a "deprecated" attribute...

    Thanks for pointing out the need of setting runtimeStyle.behavior to "none".

    Posted by Thierry on

  17. [ie-keyboard-navigation.php#comment19]

    I have become embroiled in a err.. discussion on a forum where I have been attempting to persuade a doubter that Mr Thatcher's code really does work! So far I'm losing, I fear *sad*

    In doing a little experimenting, however, (and stumbling across an error of omission), I found that the name attribute doesn't *seem* to be needed (except perhaps for backward compatibility and could, therefore, be regarded as advisory). The technique also *seems* to work with an empty anchor (that was the error of omission) provided hasLayout has been triggered by some method or other, in this case it was .layout {zoom:1;}
    Or have I got the wrong end of the stick, here?

    
    <div class="layout">
    <a id="header"></a>
    <h3>Testing absence of name attribute and empty anchor</h3>
    </div>
    

    Posted by Lorraine on

  18. [ie-keyboard-navigation.php#comment20]

    Hi Lorraine,

    Do you have a link to the discussion forum? You're correct; the name attribute is only required for older user-agents.

    Your current approach involves both a redundant container and a redundant anchor. If you're using an id as the target, why don't you apply it directly the header rather than using a redundant anchor?

    
    <div class="layout">
    <h3 id="header">
    Testing absence of name attribute and empty anchor
    </h3>
    </div>
    

    Or move the redundant anchor into the heading, and set the hasLayout property on the header?

    
    <h3>
    <a id="header"></a>
    Testing absence of name attribute and empty anchor
    </h3>
    

    The problem I have with all of these approaches is that they require redundant markup to make one browser behave correctly, or using a deprecated attribute if you're using XHTML. Unfortunately, there doesn't appear to be a clean, valid, standards compliant method of dealing with the problem, so I suppose it's up to each individual to use the approach they feel most comfortable with.

    Posted by Gez on

  19. [ie-keyboard-navigation.php#comment21]

    Hi Gez
    I appreciate what you say about my current approach. I have played around with something similar to both your suggestions and they work for me. The example I put up actually related, in part, to what was going on in that other place and, frankly, I was beginning to doubt my own eyes in my test pages.

    The forum: http://www.csscreator.com/css-forum/ftopic11823-0-asc-15.html

    It rambles hither and thither a bit, but I first dared to mention Jim Thatcher's method about half way down page 2. From then on I felt I had to justify what Mr T said "against all odds". BTW my comment Jim Thatcher (who he?) relates to the third post on the page.

    You will note the penultimate post (at this time) has a link to this article and comments, I guess that's me drummed out then, for baaad behaviour. *wink* Anything you can do to save me from being black-balled will be greatly appreciated.

    Posted by Lorraine on

  20. [ie-keyboard-navigation.php#comment22]

    Hi Lorraine,

    That's a deep thread, but it looks to me like you're winning. I don't get the, Jim Thatcher (who he?), part. Will this all make sense in the morning? *smile*

    Posted by Gez on

  21. [ie-keyboard-navigation.php#comment23]

    Jim Thatcher (who he?)
    JT was first quoted at the bottom of page 1. Then he came in for a bit of stick - sheesh! How dare they - he's one of my heroes.
    As to making sense in the morning - can anyone guarantee it will make sense - ever? *sad*
    Thank you for taking an interest - very much appreciated.

    Posted by Lorraine on

  22. [ie-keyboard-navigation.php#comment24]

    I think I am the doubter - although I would prefer sceptic *smile* The initial debate now seems resolved and for me your tabindex solution is neater and more elegant than the alternative.

    The discussion is at http://www.csscreator.com/css-forum/ftopic11823.html

    From what I can determine the hasLayout solution will only work when applied to certain elements - div and span. body, table & td are automatically hasLayout and will also work. Applying hasLayout to p, h2, em seems to have no effect on IE keyboard navigation.

    My rough and ready test page is at http://wiki.jalakai.co.uk/css-demos/ie-keyboard-navigation

    Cheers,

    Chris

    Posted by Chris on

  23. [ie-keyboard-navigation.php#comment25]

    Hi Chris,

    At this point, the hasLayout association is mainly observational and hasn't been thoroughly tested. Jim Thatcher noticed a situation where hasLayout was true on an h2 containing a redundant anchor element, but keyboard navigation didn't work as expected http://juicystudio.com/article/ie-keyboard-navigation.php#comment13

    The safest solution appears to be to either use href or tabindex. As well as the scripting solution I suggested, Terrence Wood (the person who first made the connection with hasLayout) has provided two CSS expressions in the comment section that are reliable workarounds:

    http://juicystudio.com/article/ie-keyboard-navigation.php#comment11
    http://juicystudio.com/article/ie-keyboard-navigation.php#comment17

    Posted by Gez on

  24. [ie-keyboard-navigation.php#comment26]

    Looks like the bottom line is "Advise the disabled not to use Internet Explorer".

    Other advantages for the disabled of not using IE:
    Most other browsers will enlarge text for which the CSS specifies font-size in px.
    Opera additionally zooms images.
    Other browsers are fairly safe with Javascript, Java and plug-ins enabled, while IE's only safe(-ish) if you disable everything except HTML. That means the disabled can safely use any accessibility aids built using e.g. Javascript - for example Gez' Form Help without Popups (http://www.juicystudio.com/article/form-help-without-popups.html)

    Posted by Philip Chalmers on

Comments are closed for this entry.