CSS & HTML

Style Your Ordered List With CSS Counters and ::marker

By Aigars Silkalns Updated 5 min read

Style Your Ordered List With CSS Counters and ::marker

To style an ordered list in modern CSS, use the ::marker pseudo-element when you only need to change the number’s colour or font, and use CSS counters (counter-reset, counter-increment and counter()) when you want badges, padding, backgrounds or custom formats. Both work on real ol elements, so screen readers still announce a numbered list.

The old approach was to nest a p inside every li, then give the ol one font and the paragraph another, so the numbers looked different from the text. That hack is no longer needed: you can target the number directly, and you can keep the markup as plain as <ol><li>Text</li></ol>.

Start with plain, semantic markup

<ol class="marked">
  <li>Sketch the layout</li>
  <li>Pick a type pairing</li>
  <li>Ship it</li>
</ol>

No wrapper paragraphs and no extra spans. The browser already knows these are ordered items, which is what assistive technology and search engines rely on. Everything below is CSS.

Option 1: Style the number with ::marker

The ::marker pseudo-element selects the bullet or number of a list item. It is the quickest route to the effect the original tutorial achieved, a number in a different typeface and colour from the text.

:root {
  --accent: oklch(0.55 0.2 265);
}

.marked {
  padding-inline-start: 1.75rem;
}

.marked li {
  padding-inline-start: 0.4rem;
  margin-block: 0.5rem;
}

.marked li::marker {
  font: italic 700 1.25em Georgia, serif;
  color: var(--accent);
}

Two things to know. First, ::marker accepts only a small set of properties: font properties, color, content, white-space, animations and transitions, and a few text-direction properties (see the MDN reference). You cannot give a marker a background, border, width or margin. Second, because of that limit, ::marker is perfect for typography and colour but not for badges.

Use logical properties such as padding-inline-start and margin-block so the list works unchanged in right-to-left languages.

Option 2: Build numbered badges with CSS counters

When you want a circle, a square or a large step number, hide the native marker and draw your own with a counter on ::before. A counter is a number the browser keeps for you. You create it, increment it on each item, and print its value as generated content.

.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: step;
}

.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: 2.25rem 1fr;
  gap: 0.9rem;
  margin-block: 1rem;
  align-items: start;
}

.steps li::before {
  content: counter(step);
  display: grid;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-weight: 700;
}

Read it in three parts:

  1. counter-reset: step on the list starts a counter named step at zero.
  2. counter-increment: step on each li adds one before that item renders, so the first item shows 1.
  3. content: counter(step) prints the current value inside the pseudo-element, which you can style like any other box.

Because the badge is a pseudo-element on a grid item, the text beside it stays aligned even when a step wraps onto several lines. If your steps contain a heading and a description, put them in one wrapper div so the grid has exactly two columns.

A caution about list semantics

Setting list-style: none on a list can make Safari with VoiceOver stop announcing it as a list. The badge example has a second cause: display: grid on the li replaces its list-item display, and Chrome and Safari can then drop the list semantics too. A generated counter is also not guaranteed to be read aloud the way a native marker is.

The practical fix is to restore the semantics explicitly when you strip list styling: put role="list" on the ol and, if a browser still drops the items, role="listitem" on each li. If the order matters to the reader, such as a recipe or an installation guide, you can also keep the native marker visible with ::marker and use the badge treatment only for decoration or where the number is repeated in the text.

<ol class="steps" role="list">
  <li>Sketch the layout</li>
  <li>Pick a type pairing</li>
</ol>

Option 3: Nested outlines with counters()

The plural counters() function joins the values of every nested counter with a separator, which gives you legal-style numbering such as 1.2.1 that the browser cannot produce with list-style-type alone.

.outline {
  list-style: none;
  padding: 0;
  margin: 0;
  counter-reset: sec;
}

.outline li {
  counter-increment: sec;
  margin-block: 0.3rem;
}

.outline li::before {
  content: counters(sec, ".") " ";
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.outline ol {
  list-style: none;
  padding-inline-start: 1.5rem;
  margin: 0;
  counter-reset: sec;
}

Each nested ol creates a new instance of the same sec counter, and counters() prints the whole chain. The tabular-nums setting keeps digits the same width so entries line up in a long outline.

Native attributes you may have forgotten

Before reaching for CSS, check whether HTML already does the job:

Need Use
Count down instead of up <ol reversed>
Start at a different number <ol start="5">
Override one item’s number <li value="10">
Letters or Roman numerals list-style-type: upper-roman, lower-alpha and so on
Leading zeros list-style-type: decimal-leading-zero
Marker inside the text block list-style-position: inside

The ol start, reversed and li value attributes do not affect your own named counters. To offset a custom counter, use counter-set or counter-reset: step 4. Where the number carries meaning, prefer the HTML attributes.

Custom number formats with @counter-style

If decimal and upper-roman are not enough, @counter-style lets you define your own system and then use it in both list-style-type and counter().

@counter-style circled {
  system: fixed;
  symbols: "①" "②" "③" "④" "⑤" "⑥" "⑦" "⑧" "⑨";
  suffix: " ";
}

.circled {
  list-style-type: circled;
}

With the fixed system, items beyond the ninth fall back to a normal decimal number. @counter-style is supported in Chrome 91+, Firefox 33+ and Safari 17+ (per MDN), so keep the fallback acceptable for older browsers.

If you only want to change the text of the number and keep the native marker, you do not need a pseudo-element. Every list item has a built-in list-item counter, and ::marker accepts content:

li::marker {
  content: counter(list-item) ". ";
}

Accessibility checklist

  • Keep the real ol and li elements. Do not fake numbers with spans.
  • Ensure marker and badge colours reach at least 4.5:1 contrast against the background, or 3:1 for large bold numerals.
  • Prefer ::marker when the number is the only thing you want to restyle.
  • Do not animate numbers in ways that distract. If you do, wrap the animation in @media (prefers-reduced-motion: no-preference).
  • Use logical properties so the layout flips correctly in RTL.

Where to go next

For selector techniques that pair well with counters, such as :nth-child() and :has(), read The Power of CSS Selectors.

60 comments from the archive
  1. Huh, that makes things so much easier. I knew you could stick a good deal into a list-item, but I forgot that you could style them independently also. Thanks! And lovely site! This one’s a keeper :)

  2. Sir, very useful indeed, i liked to use the ’em’ unit insted of the pixel/point, but not so sure how do you measure font size using the ’em’ ?
    one more thing, would u write a tutorial about the new DIV/CSS trend, using DIVs instead of Tables ?
    And what do you recommend to read about CSS ?
    Thanks Alot.

    God blesses

  3. Wouldn’t it be easier to just style the <li> tags instead of adding <p> tags inside the list items? Just a though… haven’t tried this, though, great idea!

  4. Justin, i tested it here (ff), and unfortunately if you use
    ol li {
    font: normal .8em Arial, Helvetica, sans-serif;
    color: #000000;
    }
    it will overwrite the “ol” style.

  5. aaahhhhh very good indeed.

    Some people will argue about the p being in there but it makes sense to style the actual listing and not font.

    @Mohammed – CSS reading I would recommend notepad. If you are after div styling tuts it should not be too hard to do so because it is now the ‘common’ way of coding for websites.

  6. Thanks for this great post. However there should be more about ul, ul li ul.classname, ul li a:hover etc ;)

    @Mohammed here you should find a chart that shows the ration of px,em and points. My way is to make em equal to pixel in body section of css
    body {
    font-size:%75;
    }
    Then 1px=1em ;)

  7. Wikkid… only thing with this is that it’d be extra work to get the p tag into the li tag when working with a CMS – especially if the site will ultimately be controlled by a client who probably couldn’t be bothered.

    I’d suggest just bolding the li and then instead of

    ol p

    style it with

    ol bold

    That will make it a bit more ‘CMS friendly’ :)

  8. MarkB, an excellent point, but sadly that just proves what I’ve said for years. A CMS should be for content database management, hence Content Management System. A CMS should have nothing to do with how the content is displayed. How content is displayed should be handled by a separate CDS, or Content Display System (a term I kinda made up myself).

    I’ve written my own universal CMS that I use for most of my clients, and I write a separate CDS for each client’s website. It’s a quick and speedy process to code a CDS when you’re already familiar with the database structure of the CMS. This way content is truly separated from the design. I don’t feel hindered in the least as to how I design and code my websites, because I never have to put up with the templating garbage that you have to deal with on most content management systems. It also works better for my clients; since my CMS has a client and webmaster mode, my clients don’t get overwhelmed by a bloated WYSIWYG editor and the bloated settings and modules that most systems today, have.

    But not everyone is familiar with server-side programming (PHP, ASP, SQL, etc.), hence they couldn’t code their own CDS even if they wanted to; so I guess I’m probably a little over-opinionated in this matter. So don’t take what I just said too seriously.

  9. Thanks Guys for your replies.
    Yes, liked the CSS although i used to use tables and still can do things fixed and nice with Tables, but with CSS still hard to align-middle, center objects, wrap columns !, i know there are alot of workaround and hacks, but still not 100% portable i guess. but am trying.

    @Heri ? are you a designer or programmer?, for your CDS, how do you split the formatting out of the CMS db ? or it is just a way of displaying the layout ? providing options of different templates or ready customized profiles of displaying the contents.
    it is still a nice idea indeed.

    Good luck.
    God Blesses.

  10. Nice tip :)
    but it is not semantically correct; if anyone cares.

  11. I don’t know you can read chinese or not .( My English is poor) Your site is so beautiful and the article is also nice for me ! (hope my word no mistakes)

  12. I’m a bit curious about your CDS, a term I have never heard before. I do like the idea though, and like MarkB points out, a p within a li is not what I expect any other than someone working with web would get. It is hard enough to make systems that makes publishing easy for customers.

    A way to go would perhaps be to render a
    *list thing
    * and another

    as a li with a p inn it when it is outputted to the screen (well, actually: swap the star with li p before insert to the db) . But: if you have some CDS that makes it possible to keep markup out of the database, that would be great. I’m not asking you to shear your code, but how to go about starting a project like that would be nice.

    I searched your page for CDS, and a lite google search, but nothing that strikes me as the answer.

  13. I’ve come across a better way (although not well supported)…but it does require less markup.

    li:first-child { color: green; }
    li { color: blue; }

    this is *supposed* to color just the numbers green.

  14. This is a tip? I fail to see what is so profound about this tip. Its just simple CSS that anybody can do. So you can italicize the numbers and change the font to something else for the text. What was so worthy about writing this as an article?

  15. @Anonymous: I actually found this tip quite useful. I wouldn’t have known how to do this otherwise, and I’ve been wantingto learn how to give list numbers and text different styles ever since I saw the cool effects on Jon Hick’s website.

    I like this website by the way, it’s very useful, although I’m not a big fan of the layout. Especially the font! *kills arial*.

  16. Why not use ‘span’ to style the list items differently? This would be a bit more semantically correct, would it not?

  17. This is great, but I’d love a tutorial on fun ways to style ul lists, like horizontal navigation. I’ve been looking online for days for different ways…!

  18. I tried to add this on my wordpress blog but wordpress removes the tag.
    So I tried to apply the “normal” style onto the element but that doesn’t work.
    Any ideas??

  19. Thanks for the tip, I already used in one of my websites (its useful to use some Javascript to add the extra markup, I used ‘span’ instead of ‘p’)

  20. When I click the “View my demo” link, a popup appears with ol p styling examples. Is the popup java? If not, how was it programed? Can this technique be done with CS3 behaviors?

  21. Great tutorials on this site!
    CSS 2.0 is very powerful to customize your html elements.

  22. Hello Looking for some code to make a customise can i have the type being (a) (b) (v) etc! like <ol type=”(a)” etc) oor

  23. Thank you for the great solution. The current design trends are for clean cust minimalist websites In such css driven designs, styling ordered lists and bullet lists presents an excellent opportunity to go that extra mile.Its interesting to see what simple formatting using css can achieve!

  24. Dear Nick:
    Thanks, thanks, thanks. Only a designer could explain “programming jargon” so clear like your posts. I love your site and the spirit on it. By the way, to be a designer you have a great understanding of programming!

    Greetings,

    Ray

  25. Do you know how to get rid of the period after the number in ordered lists? I want to style the numbers with the number only, and a circle around it, probably by putting a circular image in the background of the li.

  26. Beautiful. But is there any way to control the line height of the paragraphs in the p section to achieve a uniform look within the whole list?

  27. This means I have to manually put the text between the tags every time I make an ordered list. In wordpress 2.8 however, the tags are removed after a switch between Visual and HTML type mode. Then all my work is gone.

    Is there any function or php loop I can use to stop this? Or better yet, insert tags automatically.

  28. The designs showed here show what simple and tasteful design is all about. Another one to consider

  29. Or just use a span instead of a p. Its more semantic and it works in Firefox…

  30. Shelt, Bob is right, because P is a paragraph. Span doesn’t imply that it’s a paragraph.

  31. so with this i can also make it colored or different size but to change the single line color ill do the same but with each right ?

  32. The designs showed here show what simple and tasteful design is all about

  33. The designs showed here show what simple and tasteful design is all about. Another one to consider

  34. I just heard the gossip that since there’s some technic problems on white iphone 4, iphone 4b is going to release. It that true?

  35. I just heard the gossip that since there’s some technic problems on white iphone 4, iphone 4b is going to release. It that true?

  36. The designs showed here show what simple and tasteful design is all about

  37. I am really happy I discovered your website. This is quite an interesting read.

  38. Excessive fear, anger, and work under stress last pregnancy, infants can lead to excessive hassaslığa. Intense emotions, going through the mother’s blood circulatory system and brain functions that affect the baby in some leads to the release of chemical

  39. Hey there… I love these examples, this tutorial is excellent.

    I have a question that I haven’t seen anywhere yet (maybe I haven’t searched enough). Does anyone knows how to make an ordered list like:
    Point 1 – something
    Point 2 – another something
    Point 3 – yet another

    What I meant was to have something written before the number (in this example “Point “).
    Is this possible to be made?

    Many thanks to all! :)

    1. hey Fernando,

      Yes, it’s possible.

      Please try it :

      ol li:before{
      content:’Point ‘;
      position:absolute;
      left:-20px;
      }

      bye

  40. Thank you for another magnificent article. Where else could anyone get that type of information in such an ideal way of writing? I’ve a presentation next week, and I’m on the look for such information.

  41. Hmm, I wish you could style the list style. Or can you?

  42. I wonder if there’s a way of doing this without the extra ‘p’ markup. Or if you can somehow just traget the number in the ol as a seperate item to the rest of the list.

  43. Great web site! I genuinely love how it truly is simple on my eyes and the data are nicely written. I am wondering how I may well be notified whenever a new post has been made. I’ve subscribed to your RSS which should do the trick! Have a terrific day!

  44. You really make it appear so easy along with your presentation but I to find this topic to be actually one thing that I believe I’d by no means understand. It sort of feels too complex and very vast for me. I am having a look ahead for your next publish, I will attempt to get the cling of it!

  45. Iam using this list styles on my website – is there a way to design the list numbers with images?

  46. Not my code but hope it helps you RAM.
    ul {
    list-style-type: none;
    padding: 0px;
    margin: 0px;}

    li {
    background-image: url(sqpurple.gif);
    background-repeat: no-repeat;
    background-position: 0px 5px;
    padding-left: 14px; }
    You can also use an image to style the list with:
    ul
    {
    list-style-image: url(‘sqpurple.gif’);
    }
    but it’s not cross browser compatible.

  47. YOU ARE A GOD!!!! This is the only place I found info on this that actually worked. Even the wordpress codex was useless. THANK YOU!!!!!

  48. You really make it appear so easy along with your presentation but I to find this topic to be actually one thing that I believe I’d by no means understand. It sort of feels too complex and very vast for me. I am having a look ahead for your next publish, I will attempt to get the cling of it!

  49. I genuinely love how it truly is simple on my eyes and the data are nicely written. I am wondering how I may well be notified whenever a new post has been made. I’ve subscribed to your RSS which should do the trick! Have a terrific day!

  50. Here is a great deal I came to know in terms of discount for SoGoSurvey Yearly Enterprise Account. Goto the home page of SoGoSurvey after you’ve login to your Free Account. Click on Upgrade and select the Enterprise Package. If on the payment page you enter SOGOEN in the Promo Code section and you will see that they offer you a $499 deal instead of $590. I purchased one. Try the code, maybe you get lucky too.

Comments are closed on this article.