A HaRd challenge – styling an HR element
One of the web sites I’m currently working on has a lot of line dividers, and they had been added somewhat inconsistently. Therefore, I decided to go the semantic route and throw out all div
and p
elements, and replace them with one single class-free hr
. Oh man, did I open up a can of worms…
How to basically style a hr
Let me first show you how to basically style a hr
element with CSS:
hr{
width: 100px;
height: 4px;
/* color is for IE */
color: #2F433B;
/* background is for all other web browsers */
background: #2F433B;
/* border: 0 removes the shading in most web browsers */
border: 0;
/* A zero-margin aligns it in most web browsers */
margin: 0;
/* Aligns it in IE */
text-align: left;
}
Ok, so far so good. Let’s get to the real problem.
IE and its damn margins!
Just as with certain form elements, IE is a master (or bitch) at applying space, margin and padding that can’t be removed with normal CSS; something which works for all other web browsers on all platforms:
margin: 0;
padding: 0;
The challenging thing here is that it seems that top and bottom margins just can’t be removed in IE, no matter how hard you try. And if you think float
will help here, think again. All it did was, ironically, centering the content instead of aligning it to the left-hand side… π
One way to go here is to keep the hr
element for semantic and CSS-less reasons, and then wrap it in a div
that hides the hr
and creates a line effect with a border or background color:
<div class="hr"><hr></div>
div.hr{
height: 4px;
background: #ccc;
overflow: hidden;
}
div.hr hr{
display: none;
}
But this time, I decided not to cave and go down that road; I’m not backing down! Determined to “win”, I continued. In the web site context I was working on, I needed to have the line with a top and bottom margin of 20 pixels, so I thought (read: hoped) that having a margin wouldn’t cause any problem in IE. How wrong I were…
Apparently, if you do want a margin, just setting the desired value will, in IE, not result in what you expected (either). The basic code for a 20 pixel top and bottom margin would be:
margin: 20px 0;
However, in IE there’s two problems. For some annoying reason I can’t understand, it adds 7 pixels (even number, right?) to the top and bottom margin, and margin-collapsing also fails miserably. So, to make it work, I had to give IE a special top and bottom margin to get the wanted 20 pixel margin:
margin: 3px 0 13px;
Right about now, you probably say: eh, what?! Let me guide you: the bottom margin part is the easy one. It has a 13 pixel margin plus the magical 7 pixels IE adds, which together equals in 20 pixels. Great!
The 3 pixel top margin is first 20 pixels minus the IE-added 7 pixels, which would then be 13 pixels. However, since it fails to collapse the margin together with the preceding element’s bottom margin, it instead adds them together. Luckily for me, all the places where the line divider is used is following either a paragraph or a list with a bottom margin of 10 pixels, so you need to deduct that as well from the top margin, which leaves us with just a mere 3 pixel top margin in the CSS for IE, but with a result of 20 pixels…
The calculation:
20 (desired top margin) – 7 (IE-added-margin) – 10 (preceding element’s bottom margin) = margin-top: 3px;
(in reality, 20 pixels).
The challenge to you
Conclusively, I did win over IE in this case, since a margin was needed. All I had to do was to find the magic IE formula. The challenge, though, is:
How do you use hr
elements in IE with no margins whatsoever, and without using any container element?
Finally a solution to this endlessly annoying problem. And I thought I just was misinterpreting how hrs were meant to be styled, I should have known it was IE.
Is the magical 7 pixel IE margin consistent from situation to situation, or does this number need to be figured out every time?
It's not that difficult …
first, I've defined a margin of — guess π — -7px for hr:
<code>
hr {
margin-top: -7px;
}</code>
After that I played around with adjacent sibling selectors.
Unfortunately, <code>hr + * {}</code>
does not work. You have to know which elements are following <code>hr</code>s in your document.
Second issue: if you assign a negative margin greater than -4px (quite confusing :S, I want to say: -5px, -6px, -7px etc.) IE magically starts the long awaited CSS-calcing:
It adds the difference of the top-margin and -4 to the bottom margin. In this case it's 3 pixels (-4 – -7 = 3).
That means, we have to define a negative margin of -10px to adjacent elements:
<code>
hr + p,
hr + ul,
hr + ol {
margin-top: -10px;
}</code>
This works only in IE 7, IE6 doesn't get the selector, so one would have to add a class like "afterHR" to all those elements.
Really seems to be worth the work…
— David
by the way: is there a special that the generic font-family for <code>pre, code, kbd</code> in your stylesheet is serif? And no, I don't have Courier on my system…
+reason -that
sorry for spamming π
I'm scratching my head… there's an ideal time to use HR elements instead of borders on P, DIV, etc? Give an example case?
(One entity reference appears to be incomplete, "<".)
"hr" seems to be one of the elements where implementations differ the most. Depending on the desired styling, it can become near to impossible to get acceptable (meaning working cross-browser) results. But fortunately, "hr" is rarely really needed.
However, I suspect that markup you use here to be "too much" (though I do understand the motivation). Was there no possibility to achieve separation via other elements?
Use borders instead of background color.
<code>
hr {
clear: both;
size: 0;
width: 100px;
height: 4px;
border-top: 4px solid #2F433B;
border-right: none;
border-bottom: none;
border-left: none;
/* left align in Firefox */
margin-right: 100%;
/* left align in IE, Opera */
text-align: left;
</code>
An "hr" tag is correct semantically as a divider of information.
An example where I find "hr" to be appropriate is before a page footer where I want to use a border separating main page content from the footer. Here, the "hr" is an object separating content, not a design element. When I disable the stylesheet, the separator remains.
Mark,
From what I've seen, the 7 pixels seem to be consistent.
David,
Well, yes, it is a clever solution. I should've stated that what I'm also looking for is a solution where you don't need to know the surrounding content, and that works fine in IE 6 as well.
When I added the font CSS for those elements to this web site, I think it was for the most consistent code display. However, it's a long time ago, so I don't really know… π
Devon,
As stated in the post, I want it because it's more semantically correct. Also, there's no need then for search engines, screen readers etc trying to figure out how to index/present an element (<code>div</code>, <code>p</code> etc) with empty content, when is solely there for presentation/dividing reasons and will never contain any information.
Jens,
Thanks. Poor proof-reading of me, it's fixed now.
Humbly: in what way do you find the mark-up too much? In the HTML code in the end, it will only be: <code><hr></code>, and the CSS is just about 7 lines with formatting (except for the extra line for IE web browsers then).
There's definitely other and simple ways to achieve it with other elements, but I really wanted an <code>hr</code> element, because I think it's the most correct one in the context.
MikeC,
Not sure if anything's missing from your code, but the result of your code is (tested with a strict HTML 4 doctype, and completely without a doctype):
1) Margin applied in Firefox. Better to use <code>margin: 0;</code> and it gets aligned to the left.
2) Safari doesn't render the correct width, nor does it remove the margins.
3) Margins still applied in IE 6 (haven't got IE 7 available at the moment).
Oh, and a note to everyone: to get asterisks (*) to render correctly in the comments, please escape them using *
<code>hr { margin:0; text-align:left; !position:absolute; }</code>
Too easy. π
Hi Robert,
Yes, missing code. I was setting tags to zero. Seems like the tags surrounding the "hr" have an effect on the margins? Here's a complete strict HTML 4 doctype. (sorry, I don't have Safari available to test – tested on Firefox, IE, Opera on XP.)
<code>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>HR Test</title>
<style type="text/css">
p {
margin: 0;
padding: 0;
border: none;
}
hr {
clear: both;
width: 100px;
height: 4px;
border-top: 4px solid #2F433B;
border-right: none;
border-bottom: none;
border-left: none;
margin-right: 100%;
text-align: left;
}
</style>
</head>
<body>
<h1>This is a test</h1>
An hr test to test test test
<hr />
Additional content after the hr. Test. Test.
</body>
</html></code>
(I'm on my Mac, so can't test this…)
Since Internet Explorer seems to be treating the <code>hr</code> as text (using <code>color</code> and <code>text-align</code> properties)… would setting <code>line-height</code> and/or <code>font-size</code> work?
Perhaps:
<code>hr { font: normal 1px/1px; }
?
zcorpan,
Hmm, interesting. It does work in that sense that there's no margin visible. But if you set a background color or border on the surrounding elements (for instance. <code>p</code> elements), it will do for IE just as absolute position works: the following element will end up behind it and not below it, as desired.
MikeC,
I took your full code and tested it, and it doesn't work as intended for me. I get margins in every web browser, no matter the platform. If I change <code>margin-right: 100%;</code> to <code>margin: 0;</code>, it works in Firefox, but gets a bottom margin in Safari, and it still doesn't work in IE.
So, I'm sorry, but it just doesn't seem to work.
Ash,
I was thinking about the exact same things, but no, unfortunately that doesn't help either.
Robert I feel your pain I have almost forgotten about the HR because of consistent styling and pretty much use borders exclusively.
So I will follow this challenge closely …
I liked this CSS sprites approach from Mike Purvis a lot. It uses one image!!
See the page with the full explanation
And when images are disabled you can use borders or even background-colors …?
DannyB,
We'll see where it ends up… π
Joahn,
personally, if you use extra elements, it's no hard feat to format it any way you like it. But I want the clean, semantically correct <code>HR</code>-only solution.
Last time I used a hr I think I applied something like margin:-7px 0; to it. Could be wrong.
Anton,
Unfortunately, no. It works for the top margin, but not the bottom one (all in IE, of course).
Hi Robert,
After reading the article the other night I came to the same conclusion as Anton but it seemed so obvious that I was sure I was doing something wrong. However, my site is now pixel perfect for the first time in that particular area. Since I don't consider myself either a guru or expert I'll give you what I think might be good to know.
IE gets html4/strict and the rest get xhtml11 if they want.
The hr is stuck in between floats.
I've checked it in FF1.5 & 2.0, Opera9.1, IE5-7 on XP. No Mac π
The css looks as follows (which obviously won't work in IE7):
<code>hr {
clear:left;
margin:0;
_margin:-7px 0;
border:0;
padding:0;
height:0;
background:#FFF;
color:#FFF;
display:block;
}
</code>
It seems that it is display: block that's pulling things together which is strange since I thought hr was a block level element but we're targeting IE right? I also tried it with width and text-align and it still worked.
It'll be interesting to hear if this works or if it's some weirdness in my code doing the magic.
Cheers
Pontus,
On it's own, it doesn't work for me. I think the key phrase in your comment is:
<blockquote cite="http://www.robertnyman.com/2007/03/09/a-hard-challenge-styling-an-hr-element/#comment-42435">
The hr is stuck in between floats.
I can imagine that that might work, but what I'm looking for is a standalone solution that isn't context-sensitive whatsoever.
I use plenty of hr's to separate the main-parts of my site, but i always display: none on all of them later on, and apply borders to other elements to get the same look.
This way it's both semantically correct, and easy to style cross-browser.
I never use a div.hr or something like that, but rather add borders to existing elements placed directly above or around the hr's.
OUCH! I think I just got bitten by the IE-bug.
Please, Robert, no more HeadeRache!
Andreas,
Well, that can be one approach, but that means that you will always need to know the context th <code>hr</code> element is in, which I'm striving to get away from.
Kiper,
It sucks, doesn't it? π
Hi Mark,
I've been struggling with this issue for quite a while now.
The main problem with the 7px top margin can be fixed as stated above:
margin:0px !important; //for non-IE, !important keeps it there
margin:-7px 0; //for IE
However, on this approach we still have to fix the bottom margin.
Your assumtions that HR is handled similar to text are not 100% correct but not incorect either.
I have found out that HR actually is set to inline (as most text is).
Therefore the most obious conclusion is to reset the display to
display:block;
This actually tells IE to stop acting like a bitch and use it as a box. So, final code is:
hr {
height:1px;
display:block;
margin: 0 !important;
margin: -7px;
color:#798c7c;
background-color:#798c7c;
border:0px;
}
Works like a charm on FF and IE6 (I don't have IE7 yet so I can't guarantee for it since it depends on how IE7 acts on !important).
Hope this helped (Battle won).
Sincerely,
Michael Potra
Michael,
Thanks for your suggestion!
I haven't access to IE 7 right now, but I can confirm that it works fine in Firefox, Safari and (almost) IE 6. In IE 6, I actually get a 1 pixel gap beneath, but I guess this is as close as we can get.
I can confirm it does not work in IE7.
you get top and bottom margins.
I can also confirm that it does work in ie6/FF, but not working in ie7.
It really sucks because it forces me to use things like
<code>
<div class="line"></div>
</code>
Which is bad semantically, and not to mention if it gets used alot, it is 5 times as many characters as <hr /> or <hr>
Hours of searching for a solution to this problem and this page contains the best results sofar. I am surprised such a simple seeming thing is so difficult.
Hey use consecutive HRs to see the effect they produce in IE and firefox. That's how you are gonna see the difference. I've faced this problem too! I think that wrapping thing works for every kinda webbrowser.
Michael, contrary to what Simon said, I found that your solution actually does work in IE7 as well. So, I'd say you have solved the challenge Robert put forward! Well done, congrats and thank you!
However, what doesn't work in IE7 is to use <code>margin:0 !important</code> the way you do. Because, when IE7 is in standards-mode, then that rule will actually «win» over <code>margin:-7px</code> – like it should. (In quirks-mode that elegant trick still works.) Simply remove <code>!important</code> and insert the IE-spesifice rule via a real hack – e.g. <code>!margin:-7px;</code> or conditional comments or what ever.
Robert, in my tests I don't see that 1 pixel gap that you mention. Hence I wonder if that gap is caused be some other thing. Perhaps you can show us a page where you say the problem is?
Leif,
I have no proper test case at the moment, but if you add background colors to the <code>hr</code> elements, you should be able to see the gap.
Robert,
I made a test page on my web site. Have a look. I show you how to get more – and less – rid of the gaps.I think the reason you saw any gap on your own tests must have been because you did not try exactly the styling that Michael had. Because, as long as the height of the <code>HR</code> is only 1px, and/or as long as you use the CSS color rule to color the <code>HR</code>, then you should not see any gap with this method. (The «gap[s]» is really the borders of the «shade» variant of the horisontal ruler, as opposed to the <code>noshade</code> attribute & variant.)The trouble really only begins when you want to have a background image behind the horisontal ruler. Then, in IE, you can't use the color rule. You can then either increase the bottom-negative margin to -8px to hide the bottom gap (but not the les noticable top-gap, which you haven't mentioned btw…). Or, you can position the <code>hr</code> so you can apply the clip rule to it, and then wrap it in a <code>div</code> with <code>position:relative</code> – to get completely rid of the gaps. Example is on my page. (But I think you did not want to use any <code>div …)
*bangs head on wall*
I tried just about everything but wasn't convinced, to me the suggested solutions seem complicated and rely on kludges. Then I got this idea instead, works rather well in FF and IE7 at least… YMMV, naturally.
p.h {
margin: 0;
padding: 0;
border-width: 1px 0 0 0;
border-style: solid;
border-color: #999;
}
<p class="h">
As a bonus, text inside the (otherwise empty) paragraph will work nicely and can be styled appropriately – color: #999; font-size: 0.75em; text-align: right; etc…
[…] A HaRd challenge – styling an HR element […]
[…] ?? ????? ?????? (??? <hr /> ? ??????????????? ??????, A HaRd challenge – Styling An HR Element) ? ???????????????, ???????, ??? ????? ?????? ???????? […]
[…] A Hard challenge – styling an HR element wurde ?ber dieses Problem ausf?hrlich diskutiert, jedoch noch keine zufriedenstellende L?sung […]
Thanks a lot guys, this really helped me out. IE certainly is a PIA but the display:block trick on the HR really seems to work. At least it gives you access to the top and bottom margin styles. π
I'm sure this saved me lot's of time!