94

Is it possible to style this html ...

<ul>
    <li>Dogs</li>
    <li>Cats</li>
    <li>Lions</li>
    <li>Tigers</li>
    <li>Zebras</li>
    <li>Giraffes</li>
    <li>Bears</li>
    <li>Hippopotamuses</li>
    <li>Antelopes</li>
    <li>Unicorns</li>
    <li>Seagulls</li>
</ul>

... like this ...

enter image description here

... without adding classes to specific list items, or resorting to javascript? And if so how?

The line breaks are not fixed; the list widens to take up additional space, and list items are center aligned.

twsaef
  • 2,082
  • 1
  • 15
  • 27
  • 1
    possible duplicate of [Separators For Navigation](http://stackoverflow.com/questions/5688791/separators-for-navigation) – d4nyll Jan 23 '15 at 04:16
  • 1
    @danyll This question involves multi-line lists, that question does not, and has a solution. – twsaef May 14 '15 at 09:25
  • Similar question: [Responsive Separator for Horizontal List](https://stackoverflow.com/questions/37052659/) – myf Jul 28 '23 at 13:04

11 Answers11

112

Just

li + li::before {
    content: " | ";
}

Of course, this does not actually solve the OP's problem. He wants to elide the vertical bars at the beginning and end of lines depending on where they are broken. I will go out on a limb and assert that this problem is not solvable using CSS, and not even with JS unless one wants to essentially rewrite the browser engine's text-measurement/layout/line breaking logic.

The only pieces of CSS, as far as I can see, that "know" about line breaking are, first, the ::first-line pseudo element, which does not help us here--in any case, it is limited to a few presentational attributes, and does not work together with things like ::before and ::after. The only other aspect of CSS I can think of that to some extent exposes line-breaking is hyphenation. However, hyphenating is all about adding a character (usually a dash) to the end of lines in certain situations, whereas here we are concerned about removing a character (the vertical line), so I just can't see how to apply any kind of hyphenation-related logic, even with the help of properties such as hyphenate-character.

We have the word-spacing property, which is applied intra-line but not at line beginnings and endings, which seems promising, but it defines the width of the space between words, not the character(s) to be used.

One wonders if there's some way to use the text-overflow property, which has the little-known ability to take two values for display of overflow text at both left and right, as in

text-overflow: '' '';

but there still doesn't seem to be any obvious way to get from A to B here.

  • Yes, I am pretty sure this is the correct answer until CSS gets extended to be aware of line breaks. – twsaef Sep 04 '13 at 11:45
90

This is possible with flex-box

The keys to this technique:

  • A container element set to overflow: hidden.
  • Set justify-content: space-between on the ul (which is a flex-box) to force its flex-items to stretch to the left and right edges.
  • Set margin-left: -1px on the ul to cause its left edge to overflow the container.
  • Set border-left: 1px on the li flex-items.

The container acts as a mask hiding the borders of any flex-items touching its left edge.

.flex-list {
    position: relative;
    margin: 1em;
    overflow: hidden;
}
.flex-list ul {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: space-between;
    margin-left: -1px;
}
.flex-list li {
    flex-grow: 1;
    flex-basis: auto;
    margin: .25em 0;
    padding: 0 1em;
    text-align: center;
    border-left: 1px solid #ccc;
    background-color: #fff;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css" rel="stylesheet"/>
<div class="flex-list">
    <ul>
        <li>Dogs</li>
        <li>Cats</li>
        <li>Lions</li>
        <li>Tigers</li>
        <li>Zebras</li>
        <li>Giraffes</li>
        <li>Bears</li>
        <li>Hippopotamuses</li>
        <li>Antelopes</li>
        <li>Unicorns</li>
        <li>Seagulls</li>
    </ul>
</div>
gfullam
  • 11,531
  • 5
  • 50
  • 64
  • 43
    And it only took you three years to get the answer. Now you can finish that project! Better late, than never. ;) – gfullam Jul 31 '15 at 13:03
  • 2
    This was great. I needed a bullet character divider instead, and was able to get it with this method and a few tweaks. This could be used for any effect you can style with CSS: - relatively position the LI - add the bullet using :after and the content property - absolutely position the :after - adjust the left property on the :after to half the width of its content - adjust the negative margin on the UL to hide the bullet - adjust the right side margin to fix spacing – Blorf Jan 24 '17 at 19:21
  • 1
    this solution is good, but links at the last line seems to be stretched, is it possible to fix ? – Naveen Setty Jul 04 '17 at 19:45
  • @NaveenKumarPG There are no links in the embedded example; and adding anchor tags to create link inside each list item doesn't result in stretching that I can tell. I'm not sure what you're referring to. – gfullam Jul 18 '17 at 13:11
  • Why does this not work over two lines without the `reset.min.css`? – nilon Aug 19 '18 at 18:59
  • 1
    I had the same problem as @nilon when using Bootstrap. I ended up pulling apart the CSS reset until I found the culprit and the following was what was causing it for me: `ul, li { padding: 0; }` – LinkXXI Oct 16 '18 at 00:31
  • Here's an alternative. Instead of the extra markup, it uses a data-URI background image. http://stackoverflow.com/questions/15306108/css-styling-for-horizontal-list-with-bullet-only-between-elements – Tom Mar 31 '20 at 20:31
  • 1
    I had some `ul` padding hanging around. It's safest in `.flex-list ul` to have `padding: 0;`. – Rico Picone Feb 23 '21 at 05:27
  • 1
    @RicoPicone I am relying on StackOverflow's reset.css to provide a global `padding: 0` for lists. If you don't use a reset, or your reset doesn't apply zero-padding to lists, then it will be necessary to apply zero-padding yourself. – gfullam Feb 25 '21 at 17:10
37

Before showing the code, it's worth mentioning that IE8 supports :first-child but not :last-child, so in similar situations, you should use the :first-child pseudo-class.

Demo

#menu{
    list-style: none;
}
#menu li{
    display: inline;
    padding: 0 10px;
    border-left: solid 1px black;
}
#menu li:first-child{
    border-left: none;
}
<ul id="menu">
    <li>Dogs</li>
    <li>Cats</li>
    <li>Lions</li>
    <li>More animals</li>
</ul>
Nabil Kadimi
  • 10,078
  • 2
  • 51
  • 58
15

Use :after pseudo selector. Look http://jsfiddle.net/A52T8/1/

<ul>
    <li>Dogs</li>
    <li>Cats</li>
    <li>Lions</li>
    <li>Tigers</li>
    <li>Zebras</li>
    <li>Giraffes</li>
    <li>Bears</li>
    <li>Hippopotamuses</li>
    <li>Antelopes</li>
    <li>Unicorns</li>
    <li>Seagulls</li>
</ul>

ul li { float: left; }
ul li:after { content: "|"; padding: 0 .5em; }

EDIT:

jQuery solution:

html:

<div>
    <ul id="animals">
        <li>Dogs</li>
        <li>Cats</li>
        <li>Lions</li>
        <li>Tigers</li>
        <li>Zebras</li>
        <li>Giraffes</li>
        <li>Bears</li>
        <li>Hippopotamuses</li>
        <li>Antelopes</li>
        <li>Unicorns</li>
        <li>Seagulls</li>
        <li>Monkey</li>
        <li>Hedgehog</li>
        <li>Chicken</li>
        <li>Rabbit</li>
        <li>Gorilla</li>
    </ul>
</div>

css:

div { width: 300px; }
ul li { float: left; border-right: 1px solid black; padding: 0 .5em; }
ul li:last-child { border: 0; }

jQuery

var maxWidth = 300, // Your div max-width
    totalWidth = 0;
$('#animals li').each(function(){
    var currentWidth = $(this).outerWidth(),
        nextWidth = $(this).next().outerWidth();
    totalWidth += currentWidth;
    if ( (totalWidth + nextWidth) > maxWidth ) {
        $(this).css('border', 'none');
        totalWidth = 0;
    }
});

Take a look here. I also added a few more animals. http://jsfiddle.net/A52T8/10/

elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • 2
    This wont solve the trailing pipe on line breaks problem though - http://jsfiddle.net/A52T8/3/ – twsaef Feb 07 '12 at 06:12
  • @Dan that's basically how kinakuta's answer works, but the other way around, and it's flawed for the same reason. – twsaef Feb 07 '12 at 06:43
  • Since I was also curious about this, I crafted a solution for you, I edited my original answer. It works. – elclanrs Feb 07 '12 at 06:45
  • Oops, forgot to add the `div` width. Fixed. No down vote needed. lol my bad http://jsfiddle.net/A52T8/9/ – elclanrs Feb 07 '12 at 06:52
  • 1
    Well that's the only answer so far with the correct output, but unfortunately it requires javascript. – twsaef Feb 07 '12 at 09:48
  • Well, I don't think you can really do it with just css...I guess you'll end up using js on your website sooner or later. – elclanrs Feb 07 '12 at 16:50
  • Well, this worked for me, to eliminate the last element's line, I just set content to none at last-child: li:last-child::after { content: ""; padding: 0; } – HelpNeeder May 12 '15 at 14:41
3

I know I'm a bit late to the party, but if you can put up with having the lines left-justified, one hack is to put the pipes before the items and then put a mask over the left edge, basically like so:

li::before {
  content: " | ";
  white-space: nowrap;
}

ul, li {
  display: inline;
}

.mask {
  width:4px;
  position: absolute;
  top:8px; //position as needed
}

more complete example: http://jsbin.com/hoyaduxi/1/edit

3

This should solve the problem without using borders.

li {
  display: inline-block !important;
  list-style: none;
  margin: 0 auto;
  height: 14px;
  }
    ul li::after {
      content: " | ";
      margin: 0 10px;
    }
    ul li:last-child:after {
      content: '';
      margin: 0 10px;
    }
<div>
  <ul>
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
    <li>Four</li>
    <li>Five</li>
    <li>Six</li>
    <li>Seven</li>
    <li>Eight</li>
    <li>Nine</li>
    <li>Ten</li>
    <li>Eleven</li>
    <li>Twelve</li>
    <li>Thirteen</li>
    <li>Fourteen</li>
    <li>Fifteen</li>
    <li>Sixteen</li>
    <li>Seventeen</li>
    <li>Eighteen</li>
    <li>Nineteen</li>
    <li>Twenty</li>
    <li>Twenty One</li>
    <li>Twenty Two</li>
    <li>Twenty Three</li>
    <li>Twenty Four</li>
    <li>Twenty Five</li>
    <li>Twenty Six</li>
    <li>Twenty Seven</li>
    <li>Twenty Eight</li>
    <li>Twenty Nine</li>
    <li>Thirty</li>
  </ul>
</div>
Ziftman
  • 236
  • 3
  • 9
  • This answer is the only one that responds to the stated problem using a pure solution without using any hack like using borders or hiding something. – Laurent Simon Dec 31 '22 at 08:26
  • @LaurentSimon yes, but it still contains | at the end of a line, which was specifically stated as something the OP did not want. – Steven Linn Mar 21 '23 at 15:48
2

I came across a solution today that does not appear to be here already and which seems to work quite well so far. The accepted answer does not work as-is on IE10 but this one does. http://codepen.io/vithun/pen/yDsjf/ credit to the author of course!

.pipe-separated-list-container {
  overflow-x: hidden;
}
.pipe-separated-list-container ul {
  list-style-type: none;
  position: relative;
  left: -1px;
  padding: 0;
}
.pipe-separated-list-container ul li {
  display: inline-block;
  line-height: 1;
  padding: 0 1em;
  margin-bottom: 1em;
  border-left: 1px solid;
}
<div class="pipe-separated-list-container">
  <ul>
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
    <li>Four</li>
    <li>Five</li>
    <li>Six</li>
    <li>Seven</li>
    <li>Eight</li>
    <li>Nine</li>
    <li>Ten</li>
    <li>Eleven</li>
    <li>Twelve</li>
    <li>Thirteen</li>
    <li>Fourteen</li>
    <li>Fifteen</li>
    <li>Sixteen</li>
    <li>Seventeen</li>
    <li>Eighteen</li>
    <li>Nineteen</li>
    <li>Twenty</li>
    <li>Twenty One</li>
    <li>Twenty Two</li>
    <li>Twenty Three</li>
    <li>Twenty Four</li>
    <li>Twenty Five</li>
    <li>Twenty Six</li>
    <li>Twenty Seven</li>
    <li>Twenty Eight</li>
    <li>Twenty Nine</li>
    <li>Thirty</li>
  </ul>
</div>
brahnp
  • 2,276
  • 1
  • 17
  • 22
  • Interesting. How does it work, and is it affected by parent font-size changes? Also, this solution does not center align items :) – twsaef Feb 15 '16 at 22:27
  • Oh you are right, it only works when left aligned! It did not occur to me as that was actually what I was after in my particular case. – brahnp Feb 16 '16 at 00:22
2

One solution is to style the left border like so:

li { display: inline; }
li + li {
  border-left: 1px solid;
  margin-left:.5em;
  padding-left:.5em;
}

However, this may not give you desirable results if the entire lists wraps, like it does in your example. I.e. it would give something like:

foo | bar | baz
 | bob | bill
 | judy
greim
  • 9,149
  • 6
  • 34
  • 35
0

Slightly modified SCSS version which gives you control of the pipe | size and will eliminate padding from first and last list items while respects borders.


$pipe-list-height: 20px;
$pipe-list-padding: 15px;

.pipe-list {
    position: relative;
    overflow: hidden;
    height: $pipe-list-height;

    > ul {
        display: flex;
        flex-direction: row;

        > li {
            position: relative;
            padding: 0 $pipe-list-padding;

            &:after {
                content: " ";
                position: absolute;
                border-right: 1px solid gray;
                top: 10%;
                right: 0;
                height: 75%;
                margin-top: auto;
                margin-bottom: auto;
            }

            &:first-child {
                padding-left: 0;
            }

            &:last-child {
                padding-right: 0;

                &:after {
                    border-right: none;
                }
            }
        }
    }
}
<div class="pipe-list">
  <ul>
    <li>Link</li>
    <li>Link</li>
    <li>Link</li>
  </ul>
</div>
User_coder
  • 477
  • 1
  • 7
  • 21
-1

You can use the following CSS to solve.

ul li { float: left; }
ul li:before { content: "|"; padding: 0 .5em; }
ul li:first-child:before { content: ""; padding: 0; }

Should work on IE8+ as well.

Bijoy Anupam
  • 567
  • 1
  • 5
  • 12
  • But it does not remove the bars before each line, it only removes them on the first line. – acme Dec 16 '13 at 15:12
-1

Yes, you'll need to use pseudo elements AND pseudo selectors: http://jsfiddle.net/cYky9/

kinakuta
  • 9,029
  • 1
  • 39
  • 48
  • Nearly. That removes the end of list one, but not the end of line ones - http://jsfiddle.net/cYky9/4/ – twsaef Feb 07 '12 at 06:18
  • 1
    ah, didn't realize that was a requirement - I can't think of how this is possible without some logic involved and that means server or js. – kinakuta Feb 07 '12 at 06:20