Centered Heading Overlaying a Horizontal Line with CSS

Maybe there’s a technical term that I’m not aware of for this type of centered line-splitting heading. Whatever it’s called, I’ve used it in the new design of the footer on this site, where I’ve divided the footer into sections with headers that look something like this:

I wanted to create such headings but with certain rules and restrictions:

  • No images
  • No extra HTML
  • Scalable (that is, you could add larger text and it automatically sizes to fit)
  • Fluid
  • No JavaScript

Unfortunately, I could not figure out a way to do this while meeting all those requirements. All solutions come close, but they all require an extra element that overlays the line.

Maybe there’s something I haven’t thought of, but below I’ll outline the four methods I’ve brainstormed, including the one I’m using on the headings in my footer.

Method 1: A Pseudo-Element

With this solution, I’m adding an absolutely positioned pseudo-element inside the heading element. The pseudo-element has a top border, and it’s positioned halfway down from the top. I also wrap the heading text inside a <span> to overlap the center portion of the horizontal line. The pseudo-element is z-indexed to ensure it’s below the span.

As I’ve done with all examples, the <span> has left and right padding, to give the text some room to breathe.

Here’s the code:

h1:before {
	content: "";
	display: block;
	border-top: solid 1px black;
	width: 100%;
	height: 1px;
	position: absolute;
	top: 50%;
	z-index: 1;

h1 span {
	background: #fff;
	padding: 0 20px;
	position: relative;
	z-index: 5;

The extra span and the fact that pseudo-elements don’t work in IE6/7. But because this is decorative, it degrades into nothing, so not a bad solution if you don’t mind the extra element.

Method 2: Adjacent Sibling Selector

With this method, I’m using the adjacent sibling selector which uses a combinator to target and apply the border to the first paragraph after the heading.

Once I’ve targeted the paragraph, I give it a top border, plus a negative margin and top padding equal to the amount of the negative top margin. Again, the <span> is needed to cover the border where the text is. Because the paragraph appears after the heading/span combo, the paragraph will naturally appear under the span, so no need for z-index.

The code is minimal:

h1+p {
	border-top: solid 1px black;
	padding-top: 40px;
	margin-top: -40px;

This selector doesn’t work in IE6 and you still need the extra <span>. But if you don’t care about semantics or IE6, then this is probably the best solution.

Interestingly, if you’re not sure what element is going to appear after the heading, then you could use the universal selector, like this:

h1+* {
	border-top: solid 1px black;
	padding-top: 40px;
	margin-top: -40px;

I’m not entirely sure if that has any other drawbacks or quirky behaviour, but it did work in all browsers that support this selector.

Method 3: Linear Gradient

With this method, I’ve placed a linear gradient directly on the <h1> element. The gradient has color stops set to make the gradient appear like a simple horizontal line. Again, the extra <span> is needed, to ensure the area behind the text covers the gradient.

Here’s the code:

h1 {
	background: -moz-linear-gradient(#ffffff 0%, #ffffff 49%, #000000 50%, #000000 51%, #ffffff 52%, #ffffff 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(49%, #ffffff), color-stop(50%, #000000), color-stop(51%, #000000), color-stop(52%, #ffffff), color-stop(100%, #ffffff));
	background: -webkit-linear-gradient(#ffffff 0%, #ffffff 49%, #000000 50%, #000000 51%, #ffffff 52%, #ffffff 100%);
	background: -o-linear-gradient(#ffffff 0%, #ffffff 49%, #000000 50%, #000000 51%, #ffffff 52%, #ffffff 100%);
	background: linear-gradient(#ffffff 0%, #ffffff 49%, #000000 50%, #000000 51%, #ffffff 52%, #ffffff 100%);

This includes the old WebKit syntax. This is the method I ended up using in my footer.

Besides the extra <span>, no support in IE6-9, so potentially a large portion of your audience won’t see the effect.

Method 4: One of the Above Methods + jQuery

This method inserts the extra <span> element using jQuery, so it could be combined with any of the methods shown above. Here’s the code:

var myHeading = $('h1'),
    myHeadingText = $(myHeading).text(),
    myFullText = '' + myHeadingText + '';


There might be a few different ways to insert the <span> (remember, you need the <span> to wrap your heading text, not just be inserted next to it), but this is the method I chose.

Requires JavaScript, and there’s still an extra element — it’s just not in the HTML. If you choose to do this with Method 2, then you’ll have support everywhere except IE6 (which doesn’t support the adjacent sibling selector).

Can It Be Done Another Way?

As always, when I finish a post like this, I’m always thinking that someone’s going to point out something extremely obvious that I’ve missed. So I’d be glad to hear if anyone can think of another way to do this type of heading with pure CSS.

I’ve put all the examples in a single combined JS Bin, so feel free to mess around with it:

Advertise Here

84 Responses

  1. Lukas:

    How about:
        display: block;
        border-top: solid 1px black;
        width: 100%;
        height: 50px;
        margin-top: 25px;
        top: 50%;
        z-index: 1;
    span {
        background: #fff;
        padding: 0 20px;
        display: inline-block;
        z-index: 5;

    It doesn’t use pseudo elements, so should work in IE too

    • I can see what you’re trying to do there, and it works great in Chrome, but I can’t get it to work in IE8. It doesn’t honor the top margin setting for the span. And if you absolutely position it, it pushes the text all the way to the left.

      Here’s a JSBin with your code if anyone wants to fiddle with it:,live

      Also, IE7 doesn’t support “inline-block”, so even if you got it to work in IE8, it wouldn’t work in IE7 anyhow, which is the same as pseudo-elements.

      But nice thinking, thanks.

  2. Bryan Moreno:

    Well, I think a solution for the semantic-addict might be something like this:

    div {
    text-align: center;
    position: relative;
    h1 {
    display: inline-block;
    padding: 10px;
    background: #fff;
    text-align: center;
    h1:before {
    content: "";
    width: 100%;
    border-top: 1px solid #000;
    height: 1px;
    left: 0;
    top: 55px;
    display: block;
    position: absolute;
    background: #fff;
    z-index: -1;
    text-align: center;

    However, this requires the parent of the h1 element to be text-aligned to the center (which will make us have to reset all the child elements to the left-alignment), and have a position: relative. It’s a bit of heavy drawback have to remember to left-align all the child elements, but for those who aren’t willing to give up on semantics, might be good.

  3. What about using adjacent selector via jQuery? This way we’ve got IE6 support too.

  4. Jim:

    here’s how i did it:

    h5 {font-size:20px;border-top: 5px solid #09C;}
    h5 span { position: relative; top: -15px; padding: 10px; background: white;}

    the negative value is the border width + half the font size.
    you dont need to set it to inline-block, since side padding applies to inline elements.
    works cross-browser, as far as i know.

    • I think we have a winner here… Here’s Jim’s solution:,live

      From my testing, this works everywhere, including IE6-8. Thanks!

      • Two problems here:

        – it relies on opaque background, meaning you only have to use (known) solid colors for the parent element

        – it messes up with the vertical flow creating unwanted vertical space

        • You’re right about the colors. Good point. I might have to take back my “winner” declaration. :)

          Yeah, after seeing that drawback, I think I like all of my solutions better, because they don’t mess with the positioning of the main header element. Thanks for the feedback.

          Nonetheless, I still think this could be used in situations where color and vertical space are not an issue and where IE6+ support is needed.

        • Mike:

          With the exception of seelts’ solution, every single solution on this page relies on an opaque background on the header (or the span inside it). In theory they should all work just as well with a noise background, or anything that tiles if you’re very careful and willing to play with the background positioning a bit.

          • I’m pretty sure it wouldn’t work.

          • No, it only *seems* to work, it relies on a very tight scenario.

            I would recommend you to check it under different browsers. In doesn’t *really* work in none of the big 5.

            I can provide you with some screenshots if you think it’s necessary.

            I forgot two things:

            – consider first that no user has the same dpi and no browser renders typography the same way
            – use em not px

            … and about that tight scenario: it would *occasionally* work only if your font stack has just one font family in it.

            … and about different browsers: I also mean different platforms.

          • Jim:

            i’m not sure what the difference is between *seems* to work and *really* works.

            if it looks right in your scenario, in all modern browsers/platforms, then it’s the right solution. to me anyway, it’s about optimizing your code for the situation, not about using one blanket code snippet that’s way more complex than needed.

          • OK. Let’s make it more clear.

            It *seems* to work because he has created him self a controlled environment: one browser, one dpi, one font.

            The moment you start testing for the natural variety of platforms and browsers out there, it’s the moment when everything falls apart.

            More precisely, the line is no longer at the middle, the pattern of the h1 no longer aligns it self with the pattern on the ex# divs.

            If you really want to see it as a screenshot, I can provide you with some, but I believe you already know how to test cross browser?

            … and no, it doesn’t do that:

            “if it looks right in your scenario, in all modern browsers/platforms, then it’s the right solution.”

            That’s the whole point: the pattern test only works in one browser maybe, with one font, maybe, and one platform, maybe.

          • Mike:

            Okay, so that example was a bit rushed and easy to break. Try this more rugged version.


          • While I do appreciate your efforts, it’s still very easy to break since it still depends on a number of must have elements, like the 16px base font, among others.

            Like I said before, it only *seems* to work, it relies on a very tight scenario. The problem is by no means new, just the application. It wasn’t a good solution a decade ago and it still isn’t.

            Fortunately, these days we can consider this a progressive enhancement and make use of libraries like jQuery for this feat.

            … and let’s not forget about backgrounds with various levels of transparency.

    • Neat! Works cross-browser according to my testing as well. Thanks for that snippet. Very simple markup and styling too, nothing obtrusive.

  5. only css, use clearfix

    h1{border-bottom:3px solid #9faf41;}
    h1 span{float:left; padding:0 10px; background-color:#111; margin-left:10px; margin-top:-15px; display:block; font-size:24px}


  6. seelts:
            border-width: 1px 0 0;
            text-align: center;
            <legend>some title</legend>

    but IE can’t center the text

  7. Khalid:

    That’s funny. I created a few years ago a site with the same design for headings (well, I styled it a bit extra with a 1px box-shadow). At that moment I used extra markup. A month ago a redesigned the same site and I wanted to keep that element. But now I used a pseudo element (like the first method). For IE7 I used a crappy fallback (easy with the Paul Irish method – giving the html an extra ie7 class); border-top. It’s not ideal, but I don’t care that much about IE7.

  8. Mike:

    If even the extra span is too much, you could try giving the h1 a background, some side padding, a negative bottom margin, and then putting border-top and padding-top on the UL that’s following it. The z-index will probably need to be messed with a bit, and it depends on a structure similar to how your footer is built, but it meets all the requirements as long as the h1 doesn’t break onto two lines.

    Alternatively, if you’ve got the h1 and everything following wrapped in a single container, you could use a negative margin-top on the h1, and give the container a border-top and enough extra margin-top to compensate for the header sticking out. Assuming, of course, that this doesn’t conflict with any of the other styles on the container.

  9. I think I figured it out: JSbin

    Pro: single element
    Con: no horizontal padding padding in IE<=7, the only way to do that would be to use &nbsp; in the HTML instead of using pseudo-elements.

    And… it’s already in the wild!

    • That’s a pretty neat way to do it, and definitely great that it doesn’t require the extra element.

      But …. it’s not quite working the way you describe in IE7. Here’s how it looks in IE7 (on IETester) on that live site you linked:

      So I get the same result in IE7 on IETester; IE8 in Compatibility mode (which is basically the same as IE7), and the same in IE8 in IE7 standards mode.

      If you’ve tested in a native IE7 install and gotten a different result, then maybe it does work. But I doubt that. It seems that IE7 does support the :first-line pseudo-element, but it’s buggy.

      Nonetheless, very creative thinking there, whoever came up with that.

  10. muki:

    excuse, can I translate your article to my blog (Taiwan?)
    I’ll write your original blog address & author

    this is a very useful article, I hope can give lots of people realize it .

    thank, again.

  11. Here’s my quick take:

    Only thing is now to position side lines to be on the middle. This can be also done by using a repetitive 1px background on side spans.

    A stripped down version:

    … and side lines on the middle:

    … and w/o any extra vertical spacing:

  12. Vincenti:

    No CSS – you embed your header in a table. Works in MSIE 8 – Firefox – Google Chrome. Haven’t tested others.
    Appreciate your tutorials. Regards. g.

    <table width='100%' ><tr> <td ><hr></td><td style='padding:10' width='1'>
       <h1>Anything&nbsp;you&nbsp;wish</h1> <!-- YOUR HEADER -->
    </td><td ><hr></td> </tr></table>
    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.
  13. Vincenti:

    Reply to Louis Lazaris
    Your own code is beautiful in your eyes, but doesn’t work as well: as you shorten the text in your header, it leaves increasing white space to the left and to the right; the horizontal lines should eat up that white space. There is nothing automatic in your html or css, you have to declare the width of the text in your header. To make width handling automatic, you have to replace your <td style='padding:10px 20px 0 10px; width:260px; text-align: center;' width='1'> with <td style='padding:10px 20px 0 10px; width:0px; text-align: center;' width='1'>
    When you do that correct your problem and you also need not worry about centering the header text: the browser takes care of that. The width clause in the style attribute sets the minimum width and it is best to set the minimum to 0. regards

    • Your example doesn’t work when I do what you say here. You’re going to have to create an example so we can see your in action, otherwise I can’t see it working.

      But anyhow, thanks for the feedback, it’s interesting to see this attempted in various ways, even a table. :)

  14. We recently implemented our own solution for our company website ( )
    It should work well on almost all popular browsers

    h1 {
        border-bottom: 1px solid #EEE;
        color: #555;
        font-size: 18px;
        margin-bottom: 30px;
        position: relative;
        text-align: center;
    h1 span {
        background:  #FFF;
        padding: 0 15px;
        position: relative;
        top: 10px;
  15. cfgm:

    Wow! that’s works on FF8 too!!! ;)

  16. this is really cool stuff here…thanks for sharing such a wonderful tutorial

  17. Josh Powell:

    You kids and your new-fangled css layouts think too hard about this :)

    <table width="100%">
      <td width="50%"><hr></td>
      <td width="50%"><hr></td>
    • Greg C:

      With a little css help this is the best method I’ve used to achieve true transparency with my text.

    • Thank you i am using this one..

    • Out of all the options, this is the one I’m using too. This is because my background isn’t a solid colour.

      Just a note to most people, you’ll need a non-breaking space ( ) rather than a regular space or else the words will fall on to two/three/more lines.

      <table class="date">
      		<td class="left"><hr></td>
      		<td class="text">Dec 10</td>
      		<td class="right"><hr></td>
      .messages .date
      	font-size: 12px;
      	line-height: 16px;
      	width: 100%;
      .messages .date .text
      	padding: 0 10px;
      .messages .date .left,
      .messages .date .right
      	width: 50%;
      .messages .date hr
      	border-top-color: rgba(255,255,255,0.3);
  18. Matt:

    What really bugs me is that this should not rely on a white background or a solid color background. I suppose the only way to do it without that is to calculate the width of the heading with js.

  19. карамба:

    h1{ background: left 49% url(1pxX1px.jpg) repeat-x;}
    +span in h1

  20. dAdXeR:

    Just had to figure out a similar way to do this, however it was for e-mail template project, so the only CSS I could use was in-line styles (which I believe removes the possibility of using before).

    I was able to get a pretty viable solution by floating HR tags alongside text (using divs when/if necessary). It came out looking pretty well using regular text, although you’d have to mess with some padding when using something huge like a h1 header. You also may need to tinker with the width % of the hr’s depending on the size of your text until you find what’s best for whatever sized font you’re using.

  21. Maksim Chemerisuk:

    It’s a little bit old topic… but I want to share another solution. It doesn’t require additional markup and doesn’t have the issue with background. Browser support is IE8+, so in case of IE7 it’s probably better to find another solution.

    See demo and source code here:,live

  22. thanks alot guys, my problem is not use tag position ^^

  23. marik:

    without background-color??????

    • Lars:

      Well, seems like the last one has got serious problems in Firefox.

      • Looks fine to me in the latest Firefox. The problem seems to be that in JS Bin, the body background colour doesn’t start pure white, and so you have to select the “output” window with your cursor in order to get the white that matches the background of the headings.

  24. Ertan:

    Greetings i encountered a problem just failed to arrange the titles within the horizonatl navigartion bar in equal space ?

  25. This version doesn’t use a background color and works with wrapping (multiline) text. It won’t work in IE7 and below.

    Check it:

    * Drops mic * ;)

  26. Apptain:

    This is all kind of silly. Why not just use a fieldset and legend. 6 lines of CSS gives you the best solution possible.

              border-top:1px solid black;
            legend {
              padding: 0.2em 0.5em;
    • Because we’re not styling a set of “fields”, we’re styling a heading (h1, h2, etc). You can’t just use a fieldset in place of a heading, this would cause serious accessibility issues.

  27. Tristan Slater:

    Here’s a solution I came up with to do a similar thing, but left aligned. Not sure if it could be reworked for center alignment.

    h2 {
      font-size: 20px
      overflow: hidden /* Cuts off trailing line */
      white-space: nowrap /* Makes sure the line doesn't wrap below */
    h2:after {
      display: inline-block /* Creates an inline block to go after the text */
      content: "." /* Could be anything, but a space */
      letter-spacing: 3000px /* To make the content soooooper wide */
      border-top: 1px solid #aaa /* The border */
      height: 0 /* To hide the conent */
      position: relative /* To allow an offset for the border */
      top: 5px /* The offset */
      overflow: hidden /* Chop off ridiculously long line */

    Advantages: no extra HTML whatsoever, no need to style parent or adjacent elements, no JS, not sure how you would make it centered
    Disadvantages: only IE8 and up

    • Tristan Slater:

      Sorry, slight edit to my comment on the last line of code. That second overflow: hidden doesn’t chop the line, it hides the content.

    • Easier to throw up a demo so we can see. I did it for you:

      Yours doesn’t seem to need the second overflow:hidden, and I also had to add a margin and change the top value to a negative, to get it to center. Not bad, but would be nice to be able to do it on both sides like that with no extra element.

      • Tristan Slater:

        Yeah, I had to play around with the numbers a bit too. My original usage used ems and I tested it re-sizing text, etc and there were no problems with the center changing.

        That’s strange it works without the second overflow: hidden. Didn’t for me, but it might depend on weather or not the dot is sticking out of the h2 or not.

        If I come up with a centered solution, I’ll let you know.

        Great blog, by the way, just discovered it looking for a solution to this problem.

  28. Sabareesh:

    The second trick looks great. Thanks for sharing the tricks.

  29. Using method 1 and it works absolutely brilliantly. Thanks a lot for this Louis.

    Cheers :)

  30. Ben:
    .crossed {
        position: relative;
    		z-index: 1;
    		display: inline-block;
    		text-align: center;
    		padding: 0 40px;
    	.crossed:first-line {
    		display: block;
    		background-color: #fff;
    		padding: 0 40px;
    	.crossed::before {
    		content: " ";
    		border-top: 2px solid #000;
    		width: 100%;
    		position: absolute;
    		z-index: -1;
    		top: 50%;
    		margin-top: -1px;
    		left: 0;

    I don’t like full width lines.

  31. Delcourt:


  32. nikl:

    Working method 1 example:
    The parent element requires “position:relative” for the absolute positioning to work.

Leave a Reply

Comment Rules: Please use a real name or alias. Keywords are not allowed in the "name" field. If you use keywords, your comment will be deleted, or your name will be replaced with the alias from your email address. No foul language, please. Thank you for cooperating.

Instructions for code snippets: Wrap inline code in <code> tags; wrap blocks of code in <pre> and <code> tags. When you want your HTML to display on the page in a code snippet inside of <code> tags, make sure you use &lt; and &gt; instead of < and >, otherwise your code will be eaten by pink unicorns.