views:

340

answers:

3

Can somebody please explain this IE7 bug to me? It occurs in Standards and Quirks mode rendering, it does not occur in Firefox, Chrome or IE8 (though switching the rendering engine via IE8 developer tools will provoke it). Here's the HTML to reproduce the behavior:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
  "http://www.w3.org/TR/html4/strict.dtd"&gt;
<html>
<head>
    <title>Test</title>
    <style type="text/css">
        /* h1      { margin: 0px; } */
        ul      { padding: 0; margin: 0; list-style-type: none; }
        ul li   { float: left; width: 140px; padding: 3px; }
        div     { clear: left; padding: 3px; } 
        div, li { background-color: OrangeRed; }
        /* ul      { border: 1px solid blue; } */
    </style>
</head>
<body>
    <h1>Heading 1</h1>
    <ul>
      <li>bla 1</li><li>bla 2</li><li>bla 3</li>
    </ul>
    <div>yada</div>
</body>
</html>
  • This renders a floated <ul> above a <div> (supposed to be a tabbed user interface).
  • There's an unexplained gap between the <div> and the <ul>.
  • Now do one of the following:
    1. Uncomment the CSS rule for <h1>. The gap disappears and the list is rendered tight to the <div>, but also very close to the <h1>.
    2. Alternatively, uncomment the CSS rule for <ul>. Now a narrow blue border is rendered above the <ul>, but the gap disappears.

My questions:

  1. How can the <h1> margin (I suppose any block level element with a defined margin will do) affect the space below the list?
  2. Can I prevent this from happening without having to set header margins to 0 or messing with the <ul> borders (setting border-width: 0; does not work BTW)?

I suppose it is connected to the <ul> itself having no height because it has only floated children. Maybe someone with more insight into IE7 peculiarities than I have can explain what the rendering engine is doing here. Thanks!

A: 

That's a really bizarre problem, IE seems to be full of these delights. I haven't found out exactly why it's deciding to render like that but with proper clearing of the floats you can usually avoid all of this trouble. The following code seems to give some consistency (in other words it's the same with or without the H1 css rule).

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
  "http://www.w3.org/TR/html4/strict.dtd"&gt;
<html>
<head>
    <title>Test</title>
     <style type="text/css">
        h1      { margin: 0px; }
        ul      { padding: 0; margin: 0; list-style-type: none;}
        ul li   { float: left; width: 140px; padding: 3px; }
        div, li { background-color: OrangeRed; }
        ul      { border: 1px solid blue; }
        .clearfix:after {
        content: ".";
        display: block;
        height: 0;
        clear: both;
        visibility: hidden;
        }

        .clearfix {display: inline-block;}  /* for IE/Mac */
    </style>
</head>
<body>
    <h1>Heading 1</h1>
    <div class="clearfix">
      <ul class="t">
        <li>bla 1</li><li>bla 2</li><li>bla 3</li>
      </ul>
    </div>
    <div>yada</div>
</body>
Simon
I'm sorry to say that this does not give me the correct result. The "tab row" spans the entire page with this solution, but it should be only three tabs wide. Can you adapt the answer in this regard?
Tomalak
+1  A: 

It's the Incorrect Float Shrink-Wrap Bug. The linked article explains the issue. It also affects IE6 btw.

As Sjaak Priester, the person whom Gérard Talbot credits for the bug, reasons is that IE first renders the floated element on the same line as the previous float, then sees clear and clears it under but fails to redraw the text.

One of the common solutions is the clearfix hack as answered by someone else here, or placing an empty clearing element after the block with the floats, like a <br style="clear:left;">. Put it between the ul and the div. This way IE will force the clear before reaching the div.

BalusC
Sorry to say that `display: inline;` does not work because the `<li>` lose their width.
Tomalak
I overlooked that bit, sorry, it was just from top of head. I updated the answer.
BalusC
+1 for your insight. I'm not sure if that is the right bug, though…? It says "[…] that floats that precede floats and have `clear` set do not shrink-wrap properly" - I don't have elements in my sample that fit that description, or do I?
Tomalak
The last `div` has a `clear: left;`.
BalusC
But cleared elements don't implicitly float. So the `<div>` does not match the description. Also, I'm not sure if incorrect shrink-wrapping is the actual problem, since rendering looks okay, instead the margin of the preceding-sibling is repeated.
Tomalak
+1  A: 

I've come up with a solution that is what seems like a good compromise. It's based on the fact that setting a border on the <ul> solves the problem, while the margin-bottom of the preceding-sibling block-level element obviously causes it.

So setting a border-top: 1px solid transparent; on the <ul> displaces it by merely one pixel, which is okay with me. As BalusC rightly points out in the comments, setting margin-top: -1px; would counteract the displacement.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
  "http://www.w3.org/TR/html4/strict.dtd"&gt;
<html>
<head>
    <title>Test</title>
    <style type="text/css">
        ul      { padding: 0; margin: 0; border-top: 1px solid transparent; list-style-type: none; }
        ul li   { float: left; width: 140px; background-color: red; }
        div     { clear: left; background-color: red; } 
    </style>
</head>
<body>
    <h1>Heading 1</h1>
    <ul>
      <li>bla 1</li><li>bla 2</li><li>bla 3</li>
    </ul>
    <div>yada</div>
</body>
</html>

I admit that this is a bit of hackery, too; it requires remembering what the bogus border is for, which is not much better than the usual CSS tricks (but a little).


Previous version of the answer: I've fixed it like this for now (seems it works across browsers and does not require CSS hackery)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
  "http://www.w3.org/TR/html4/strict.dtd"&gt;
<html>
<head>
    <title>Test</title>
    <style type="text/css">
        div.t ul    { padding: 0; margin: 0; list-style-type: none; }
        div.t ul li { float: left; width: 140px; background-color: red; }
        div.c       { background-color: red;  } 
    </style>
</head>
<body>
    <h1>Heading 1</h1>
    <div class="t">
      <ul>
        <li>bla 1</li><li>bla 2</li><li>bla 3</li>
      </ul>
      <br style="clear: left;">
    </div>
    <div class="c">yada</div>
</body>
</html>

I don't like this solution very much because the of the extra elements it requires. But I dislike dirty CSS tricks even more.

Tomalak
That doesn't fix the problem. The `<div class="t">` is actually unnecessary and you need to replace that clearing `<div>` by a `<br>` with the same style. Also see my updated answer.
BalusC
@BalusC: Hm, but *does* seem to fix the problem for me, I've tested it. I'm not sure what the `<br>` would buy me - why can't it be a `<div>`?
Tomalak
The exact snippet has still the same problem in IE6/7. I copypasted and tested it. The new solution is also a nice one. You could by the way counteract the border with a `margin-top: -1px;`.
BalusC
@BalusC: You are right, it works with `<br>`, but not with `<div>`, I'll update my answer. Good tip regarding the `margin-top`. Is there such a thing as CSS conditional comments?
Tomalak
You can find several [here](http://paulirish.com/2009/browser-specific-css-hacks/).
BalusC