133

I want to make "grammatically correct" lists using CSS. This is what I have so far:

The <li> tags are displayed horizontally with commas after them.

li { display: inline; list-style-type: none; }

li:after { content: ", "; }

That works, but I want the "last-child" to have a period instead of a comma. And, if possible, I'd like to put "and" before the "last-child" as well. The lists I'm styling are generated dynamically, so I can't just give the "last-children" a class. You can't combine pseudo-elements, but that's basically the effect I want to achieve.

li:last-child li:before { content: "and "; }

li:last-child li:after { content: "."; }

How do I make this work?

Derek
  • 1,449
  • 2
  • 10
  • 6
  • 4
    you really shouldnt be using css for this. – Funky Dude Feb 28 '10 at 16:09
  • 2
    I must agree with Funky Dude a bit. It would be better to do this in the HTML (or codebehind if it's more than plain HTML). CSS is for formatting :) – Yvo Feb 28 '10 at 16:18
  • 16
    I...personally, tend to argue that, in a list, the formatting is a nicety, not a necessity. That being the case using CSS allows for more simple additions or removals from the list, than using the html or server-side coding. But I'm strange like that, sometimes, and, honestly ymmv, etc... =) – David Thomas Feb 28 '10 at 19:02
  • 10
    And +1 for using the Oxford Comma! :) – Brandon Rhodes Nov 15 '11 at 13:36
  • 5
    +1 for "+1 for Oxford Comma". Never heard about it (I'm not native). But quick wiki lookup says it's natural, because it's not used in many languages. Funny what one can learn stack-googling CSS issues ;) – jakub.g Jul 12 '12 at 12:46

8 Answers8

182

This works :) (I hope multi-browser, Firefox likes it)

li { display: inline; list-style-type: none; }
li:after { content: ", "; }
li:last-child:before { content: "and "; }
li:last-child:after { content: "."; }
<html>
  <body>
    <ul>
      <li>One</li>
      <li>Two</li>
      <li>Three</li>
    </ul>
  </body>
</html>
Yvo
  • 18,681
  • 11
  • 71
  • 90
  • 2
    A similar problem is the case where you are separating menu items with pipe characters. The same solution works. But, the final content attribute needs to be set to {content:none;} in order to eliminate the final pipe character. – aridlehoover Aug 19 '11 at 11:07
  • 2
    Order is important too. `li:last-child:after` works but `li:after:last-child` does not. – Richard Ayotte Oct 19 '11 at 14:48
  • 1
    Have in mind that `:last-child` doesn't belong to CSS3 spec, so it's not supported by IE8 and previous versions. – Cthulhu Jul 17 '12 at 10:50
  • @Cthulhu "it's not supported by IE8" is true but "doesn't belong to CSS3 spec" is not. – Bob Kline May 14 '21 at 14:53
  • @BobKline That comment is from 9 years ago, so maybe the information is not updated. – Cthulhu May 28 '21 at 13:37
  • The :last-child pseudo-class goes back well before 2012. In fact, it's in the very first draft of the CSS3 spec. See https://www.w3.org/TR/2001/CR-css3-selectors-20011113/#last-child-pseudo, or even https://www.w3.org/TR/1999/WD-CSS3-selectors-19990803#structural-pseudos. – Bob Kline May 28 '21 at 18:03
29

I do like this for list items in <menu> elements. Consider the following markup:

<menu>
  <li><a href="/member/profile">Profile</a></li>
  <li><a href="/member/options">Options</a></li>
  <li><a href="/member/logout">Logout</a></li>
</menu>

I style it with the following CSS:

menu > li {
  display: inline;
}

menu > li::after {
  content: ' | ';
}

menu > li:last-child::after {
  content: '';
}

This will display:

Profile | Options | Logout

And this is possible because of what Martin Atkins explained on his comment

Note that in CSS 2 you would use :after, not ::after. If you use CSS 3, use ::after (two semi-columns) because ::after is a pseudo-element (a single semi-column is for pseudo-classes).

Gabriel Hautclocq
  • 3,230
  • 2
  • 26
  • 31
18

An old thread, nonetheless someone may benefit from this:

li:not(:last-child)::after { content: ","; }
li:last-child::after { content: "."; }

This should work in CSS3 and [untested] CSS2.

user5341733
  • 181
  • 1
  • 2
11

You can combine pseudo-elements! Sorry guys, I figured this one out myself shortly after posting the question. Maybe it's less commonly used because of compatibility issues.

li:last-child:before { content: "and "; }

li:last-child:after { content: "."; }

This works swimmingly. CSS is kind of amazing.

Derek
  • 1,449
  • 2
  • 10
  • 6
  • 13
    This is a bit of a "nit-picker's corner", but "last-child" is actually a pseudo-**class**, while "before" and "after" are pseudo-elements. The difference is that a pseudo-class is an additional constraint on an existing element, while a pseudo-element is effectively an entirely new element in the presentation tree that was generated in CSS rather than in HTML. You can combine these for this reason: the `last-child` is a modifier on the `li`, and then after you've selected all `li` elements that are last children you then select (and implicitly create) a new element before and after each. – Martin Atkins Nov 15 '11 at 14:52
  • I know you've probably long forgotten this particular thread, but since you mentioned that your lists were of variable length it's worth pointing out that, in most contexts, a list with exactly two items wouldn't be correct with a comma in English. In other words, if you wanted "bread and butter." rather than "bread, and butter." - then most of the solutions provided here would be incomplete. I did come up with a way around it, and posted it below as an answer, in case it helps anyone still looking this far down the thread. – Matt Wagner Oct 17 '18 at 00:38
6

Just To mention, in CSS 3

:after

should be used like this

::after

From https://developer.mozilla.org/de/docs/Web/CSS/::after :

The ::after notation was introduced in CSS 3 in order to establish a discrimination between pseudo-classes and pseudo-elements. Browsers also accept the notation :after introduced in CSS 2.

So it should be:

li { display: inline; list-style-type: none; }
li::after { content: ", "; }
li:last-child::before { content: "and "; }
li:last-child::after { content: "."; }
Wolfgang Blessen
  • 900
  • 10
  • 29
  • 3
    Every browser that supports the double colon `::` CSS3 syntax also supports just the `:` syntax, but IE 8 only supports the single-colon, so for now, it's recommended to just use the single-colon for best browser support. `::` is the newer format indented to distinguish pseudo content from pseudo selectors. If you don't need IE 8 support, feel free to use the double-colon. From this [article](https://css-tricks.com/almanac/selectors/a/after-and-before/) – JohnnyQ Jul 26 '16 at 01:33
  • 2
    IE 8 is no longer a player in terms of browser standards. Not supported by Microsoft and doesn't support HTML5 or CSS3 (pseudo elements, transforms, etc.) I used to do lots of work on backward-compatibility, up to a year ago, going all the way back to IE6/IE7 (via Modernizr.) We've come a long way, and if you intend for your site to present its online identity long-term - without it becoming a short-sighted solution - then simply consider the revenue that you would earn from someone using IE8. Nada. Even senior citizens, 20-years behind the times, are using tablets and 2-in-1 laptops now. – Steven Ventimiglia May 25 '17 at 16:52
3

Adding another answer to this question because I needed precisely what @derek was asking for and I'd already gotten a bit further before seeing the answers here. Specifically, I needed CSS that could also account for the case with exactly two list items, where the comma is NOT desired. As an example, some authorship bylines I wanted to produce would look like the following:

One author: By Adam Smith.

Two authors: By Adam Smith and Jane Doe.

Three authors: By Adam Smith, Jane Doe, and Frank Underwood.

The solutions already given here work for one author and for 3 or more authors, but neglect to account for the two author case—where the "Oxford Comma" style (also known as "Harvard Comma" style in some parts) doesn't apply - ie, there should be no comma before the conjunction.

After an afternoon of tinkering, I had come up with the following:

<html>
 <head>
  <style type="text/css">
    .byline-list {
      list-style: none;
      padding: 0;
      margin: 0;
    }
    .byline-list > li {
      display: inline;
      padding: 0;
      margin: 0;
    }
    .byline-list > li::before {
      content: ", ";
    }
    .byline-list > li:last-child::before {
      content: ", and ";
    }
    .byline-list > li:first-child + li:last-child::before {
      content: " and ";
    }
    .byline-list > li:first-child::before {
      content: "By ";
    }
    .byline-list > li:last-child::after {
      content: ".";
    }
  </style>
 </head>
 <body>
  <ul class="byline-list">
   <li>Adam Smith</li>
  </ul>
  <ul class="byline-list">
   <li>Adam Smith</li><li>Jane Doe</li>
  </ul>
  <ul class="byline-list">
   <li>Adam Smith</li><li>Jane Doe</li><li>Frank Underwood</li>
  </ul>
 </body>
</html>

It displays the bylines as I've got them above.

In the end, I also had to get rid of any whitespace between li elements, in order to get around an annoyance: the inline-block property would otherwise leave a space before each comma. There's probably an alternative decent hack for it but that isn't the subject of this question so I'll leave that for someone else to answer.

Fiddle here: http://jsfiddle.net/5REP2/

Matt Wagner
  • 100
  • 8
2

To have something like One, two and three you should add one more css style

li:nth-last-child(2):after {
    content: ' ';
}

This would remove the comma from the second last element.

Complete Code

li {
  display: inline;
  list-style-type: none;
}

li:after {
  content: ", ";
}

li:nth-last-child(2):after {
  content: '';
}

li:last-child:before {
  content: " and ";
}

li:last-child:after {
  content: ".";
}
<html>

<body>
  <ul>
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
  </ul>
</body>

</html>
Raj Nandan Sharma
  • 3,694
  • 3
  • 32
  • 42
0

I am using the same technique in a media query which effectively turns a bullet list into an inline list on smaller devices as they save space.

So the change from:

  • List item 1
  • List item 2
  • List item 3

to:

List Item 1; List Item 2; List Item 3.

  • He is trying to do this with CSS. Your method is hard coded HTML. This method is normally used to display Navigation. – Sparatan117 Apr 09 '14 at 19:25