Styling Counters in CSS
CSS-Tricks
·
Mar 17, 2025
·
article
Yes, you are reading that correctly: This is indeed a guide to styling counters with CSS. Some of you are cheering, “Finally!”, but I understand that the vast majority of you are thinking, “Um, it’s just styling lists.” If you are part of the second group, I get it. Before learning and writing more and more about counters, I thought the same thing. Now I am part of the first group, and by the end of this guide, I hope you join me there.
There are _many_ ways to create and style counters, which is why I wanted to write this guide and also how I plan to organize it: going from the most basic styling to the top-notch level of customization, sprinkling in between some sections about spacing and accessibility. It isn’t necessary to read the guide in order — each section should stand by itself, so feel free to jump to any part and start reading.
## Table of Contents
1. [HTML Based Customization](#aa-html-based-customization)
2. [Styling Simple Counters in CSS](#aa-styling-simple-counters-in-css)
3. [Custom Counters](#aa-custom-counters)
4. [Custom Counters Styles](#aa-custom-counter-styles)
5. [Images in Counters](#aa-images-in-counters)
6. [Spacing Things out](#aa-spacing-things-out)
7. [Accessibility](#aa-accessibility)
8. [Almanac references](#aa-almanac-references)
9. [Further reading](#aa-further-reading)
## Customizing Counters in HTML
Lists elements were among the [first 18 tags that made up HTML](https://www.w3.org/History/19921103-hypertext/hypertext/WWW/MarkUp/Tags.html). Their representation wasn’t defined yet but deemed fitting a bulleted list for unordered lists, and a sequence of numbered paragraphs for an ordered list.
Cool but not enough; soon people needed more from HTML alone and new **list attributes** were added throughout the years to fill in the gaps.
### `start`
The `start` attribute takes an integer and sets from where the list should start:
```
- Bread
- Milk
- Butter
- Apples
```
1. Bread
2. Milk
3. Butter
4. Apples
Although, it isn’t limited to positive values; zero and negative integers are allowed as well:
```
- Bread
- Milk
- Butter
- Apples
- Bread
- Milk
- Butter
- Apples
```
1. Bread
2. Milk
3. Butter
4. Apples
1. Bread
2. Milk
3. Butter
4. Apples
### `type`
We can use the `type` attribute to change the counter’s representation. It’s similar to CSS’s `list-style-type`, but it has its own limited uses and shouldn’t be used interchangeably\*. Its possible values are:
* `1` for decimal numbers (default)
* `a` for lowercase alphabetic
* `A` for uppercase alphabetic
* `i` for lowercase Roman numbers
* `I` for uppercase Roman numbers
```
- Bread
- Milk
- Butter
- Apples
- Bread
- Milk
- Butter
- Apples
```
1. Bread
2. Milk
3. Butter
4. Apples
1. Bread
2. Milk
3. Butter
4. Apples
It’s weird enough to use `type` on `ol` elements, but it still has some use cases\*. However, usage with the `ul` element is downright deprecated.
### `value`
The `value` attribute sets the value for a specific `li` element. This also affects the values of the `li` elements after it.
```
- Bread
- Milk
- Butter
- Apples
```
1. Bread
2. Milk
3. Butter
4. Apples
### `reversed`
The `reversed` attribute will start counting elements in reverse order, so from highest to lowest.
```
- Bread
- Milk
- Butter
- Apples
```
1. Bread
2. Milk
3. Butter
4. Apples
### All can be combined
If you ever feel the need, all list attributes can be combined in one (ordered) list.
```
- Bread
- Milk
- Butter
- Apples
```
1. Bread
2. Milk
3. Butter
4. Apples
### \* Do we need them if we now have CSS?
Funny enough, the [first CSS specification](https://www.w3.org/TR/REC-CSS1-961217#list-style-type) already included `list-style-type` and other properties to style lists, and it was released before [HTML 3.2](https://www.w3.org/TR/2018/SPSD-html32-20180315/#ol) — the first HTML spec that included some of the previous list attributes. This means that at least on paper, we had CSS list styling before HTML list attributes, so the answer isn’t as simple as “they were there before CSS.”
Without CSS, a static page (such as this guide) won’t be pretty, but at the very least, it should be readable. For example, the `type` attribute ensures that styled ordered lists won’t lose their meaning if CSS is missing, which is especially useful in legal or technical documents. Some attributes wouldn’t have a CSS equivalent until years later, including `reversed`, `start` and `value`.
## Styling Simple Counters in CSS
For most use cases, styling lists in CSS doesn’t take more than a couple of rules, but even in that brevity, we can find different ways to style the same list.
### `::marker` or `::before`?
The [::marker](https://css-tricks.com/almanac/pseudo-selectors/m/marker/) pseudo-element represents the counter part of a list item. As a pseudo-element, we can set its [content](https://css-tricks.com/almanac/properties/c/content/) property to any string to change its counter representation:
```
li::marker {
content: "💜 ";
}
```
* Bread
* Milk
* Butter
* Apples
The `content` in pseudo-elements also accepts images, which allows us to create custom markers:
```
li::marker {
content: url("./logo.svg") " ";
}
```
* bread
* milk
* butter
* apples
By default, only `li` elements have a `::marker` but we can give it to any element by setting its [display](https://css-tricks.com/almanac/properties/d/display/) property to `list-item`:
```
h4 {
display: list-item;
}
h4::marker {
content: "◦ ";
}
```
This will give each `h`4 a `::marker` which we can change to any string:
#### List Title
However, `::marker` is an odd case: it was described in the CSS spec more than 20 years ago, but only gained somewhat reliable support in 2020 and still isn’t fully supported in Safari. What’s worst, only font-related properties (such as `font-size` or `color`) are allowed, so we can’t change its `margin` or `background-color`.
This has led many to use [::before](https://css-tricks.com/almanac/pseudo-selectors/b/after-and-before/) instead of `::marker`, so you’ll see a lot of CSS in which the author got rid of the `::marker` using `list-style-type: none` and used `::before` instead:
```
li {
/* removes ::marker */
list-style-type: none;
}
li::before {
/* mimics ::marker */
content: "▸ ";
}
```
### `list-style-type`
The [list-style-type](https://css-tricks.com/almanac/properties/l/list-style/) property can be used to replace the `::marker`‘s string. Unlike `::marker`, `list-style-type` has been around forever and is most people’s go-to option for styling lists. It can take _a lot_ of different counter styles that are built-in in browsers, but you will probably use one of the following:
For unordered lists:
* `disc`
* `circle`
* `square`
```
ul {
list-style-type: square;
}
ul {
list-style-type: circle;
}
```
* bread
* milk
* butter
* apples
For ordered lists:
* `decimal`
* `decimal-leading-zero`
* `lower-roman`
* `upper-roman`
* `lower-alpha`
* `upper-alpha`
```
ol {
list-style-type: upper-roman;
}
ol {
list-style-type: lower-alpha;
}
```
1. bread
2. milk
* butter
* apples
You can find a full list of [valid counter styles here](https://drafts.csswg.org/css-counter-styles-3/#predefined-counters).
It can also take `none` to remove the marker altogether, and since not long ago, it can also take a `` for `ul` elements.
```
ul {
list-style-type: none;
}
ul {
list-style-type: "➡️ ";
}
```

## Creating Custom Counters
For a long time, there wasn’t a CSS-equivalent to the HTML `reverse`, `start` or `value` attributes. So if we wanted to reverse or change the start of multiple lists, instead of a CSS class to rule them all, we had to change their HTML one by one. You can imagine how repetitive that would get.
Besides, list attributes simply had their limitations: we can’t change how they increment with each item and there isn’t an easy way to attach a prefix or suffix to the counter. And maybe the biggest reason of all is that there wasn’t a way to number things that weren’t lists!
Custom counters let us number any collection of elements with a whole new level of customization. The workflow is to:
1. Initiate the counter with the [counter-reset](https://css-tricks.com/almanac/properties/c/counter-reset/) property.
2. Increment the counter with the [counter-increment](https://css-tricks.com/almanac/properties/c/counter-increment/) property.
3. Individually set the counters with the [counter-set](https://css-tricks.com/almanac/properties/c/counter-set/) property.
4. Output the counters with either the [counter()](https://css-tricks.com/almanac/functions/c/counter/) and [counters()](https://css-tricks.com/almanac/functions/c/counters/) functions.
As I mentioned, we can make a list out of any collection of elements, and while this has its accessibility concerns, just for demonstration’s sake, let’s try to turn a collection of headings like this…
```
The Old Buccaneer
The Sea Cook
My Shore Adventure
The Log Cabin
My Sea Adventure
Captain Silver
```
…into something that looks list-like. But just because we can make an element look like a list doesn’t always mean we should do it. Be sure to consider how the list will be announced by assistive technologies, like screen readers, and see the [Accessibility](#aa-accesibility) section for more information.
### Initiate counters: `counter-reset`
The [counter-reset](https://css-tricks.com/almanac/properties/c/counter-reset/) property takes two things: the name of the counter as a custom ident and the initial count as an integer. If the initial count isn’t given, then it will start at `0` by default:
```
.index {
counter-reset: index;
/* The same as */
counter-reset: index 0;
}
```
You can initiate several counters at once with a space-separated list and set a specific value for each one:
```
.index {
counter-reset: index another-counter 2;
}
```
This will start our `index` counter at `0` (the default) and `another-counter` at `2.`
### Set counters: `counter-set`
The [counter-set](https://css-tricks.com/almanac/properties/c/counter-set/) works similar to `counter-reset`: it takes the counter’s name followed by an integer, but this time it will set the count for that element onwards. If the integer is omitted, it will set the counter to `0` by default.
```
h2:nth-child(2) {
counter-set: index;
/* same as */
counter-set: index 0;
}
```
And we can set several counters at once, as well:
```
h2:nth-child(3) {
counter-set: index 5 another-counter 10;
}
```
This will set the third `h2` element’s index count to `5` and `another-counter` to `10`.
If there isn’t an active counter with that name, `counter-set` will initiate it at `0`.
### Increment counters: `counter-increment`
Right now, we have our counter, but it will stagnate at `0` since we haven’t set which elements should increment it. We can use the counter-increment property for that, which takes the name of the counter and how much it should be incremented by. If we only write the counter’s name, it will increment it by `1`.
In this case, we want each `h2` title to increment the counter by one, and that should be as easy as setting `counter-increment` to the counter’s name:
```
h2 {
counter-increment: index;
/* same as */
counter-increment: index 1;
}
```
Just like with `counter-reset`, we can increment several counters at once in a space-separated list:
```
h2 {
counter-increment: index another-counter 2;
}
```
This will increment `index` by one and `another-counter` by two on each `h2` element.
If there isn’t an active counter with that name, `counter-increment` will initiate it at `0`.
### Output simple lists: `counter()`
So far, we won’t see any change in the counter representation. The counters are _counting_ but not _showing_, so to output the counter’s result we use the [counter()](https://css-tricks.com/almanac/functions/c/counter/) and [counters()](https://css-tricks.com/almanac/functions/c/counters/) functions. Yes, those are two functions with similar names but important differences.
The `counter()` function takes the name of a counter and outputs its content as a string. If many active counters have the same name, it will select the one that is defined closest to the element, so we can only output one counter at a time.
As mentioned earlier, we can set an element’s `display` to `list-item` to work with its `::marker` pseudo-element:
```
h2 {
display: list-item;
}
```
Then, we can use `counter()` in its `content` property to output the current count. This allows us to prefix and suffix the counter by writing a string before or after the `counter()` function:
```
h2::marker {
content: "Part " counter(index) ": ";
}
```

Alternatively, we can use the everyday `::before` pseudo-element to the same effect:
```
h2::before {
content: "Part " counter(index) ": ";
}
```
### Output nested lists: `counters()`
`counter()` works great for most situations, but what if we wanted to do a nested list like this:
```
1. Paradise Beaches
1.1. Hawaiian Islands
1.2. Caribbean Getaway
1.2.1. Aruba
1.2.2. Barbados
2. Outdoor Escapades
2.1 National Park Hike
2.2. Mountain Skiing Trip
```
We would need to initiate individual counters and write different `counter()` functions for each level of nesting, and that’s only possible if we know how deep the nesting goes, which we simply don’t at times.
In this case, we use the `counters()` function, which also takes the name of a counter as an argument but instead of just outputting its content, it will join all active counters with that name into a single string and output it. To do so, it takes a string as a second argument, usually something like a dot (`"."`) or dash (`"-"`) that will be used between counters to join them.
We can use `counter-reset` and `counter-increment` to initiate a counter for each `ol` element, while each `li` will increment its closest counter by 1:
```
ol {
counter-reset: item;
}
li {
counter-increment: item;
}
```
But this time, instead of using `counter()` (which would only display one counter per item), we will use `counters()` to join all active counters by a string (e.g. `".`“) and output them at once:
```
li::marker {
content: counters(item, ".") ". ";
}
```

### Styling Counters
Both the `counter()` and `counters()` functions accept one additional, yet optional, last argument representing the counter style, the same ones we use in the `list-style-type` property. So in our last two examples, we could change the counter styles to Roman numbers and alphabetic letters, respectively:
```
h2::marker {
content: "Part " counter(index, upper-roman) ": ";
}
```

```
li::marker {
content: counters(item, ".", lower-alpha) ". ";
}
```

### Reverse Counters
It’s possible to count backward using custom counters, but we need to know beforehand the number of elements we’ll count. So for example, if we want to make a Top Five list in reversed order:
```
Best rated animation movies
- Toy Story 2
- Toy Story 1
- Finding Nemo
- How to Train your Dragon
- Inside Out
```
We have to initiate our counter at the total number of elements plus one (so it doesn’t end at `0`):
```
ol {
counter-reset: movies 6;
}
```
And then set the increment to a negative integer:
```
li {
counter-increment: movies -1;
}
```
To output the count we use `counter()` as we did before:
```
li::marker {
content: counter(movies) ". ";
}
```

There is also a way to write reversed counters supported in Firefox, but **it hasn’t shipped to any other browser**. Using the `reversed()` functional notation, we can wrap the counter name while initiating it to say it should be reversed.
```
ol {
counter-reset: reversed(movies);
}
li {
counter-increment: movies;
}
li::marker {
content: counter(movies) " .";
}
```
## Styling Custom Counters
The last section was all about custom counters: we changed from where they started and how they increased, but at the end of the day, their output was styled in one of the browser’s built-in counter styles, usually `decimal`. Now using [@counter-style](https://css-tricks.com/almanac/rules/c/counter-style/), we’ll build our own counter styles to style any list.
The `@counter-style` at-rule, as its name implies, lets you create custom counter styles. After writing the at-rule it takes a custom ident as a name:
```
@counter-style my-counter-style {
/* etc. */
}
```
That name can be used inside the properties and functions that take a counter style, such as `list-style-type` or the last argument in `counter()` and `counters()`:
```
ul {
list-style-type: my-counter-style;
}
li::marker {
content: counter(my-counter, my-counter-style) ". ";
}
```
What do we write inside `@counter-style`? Descriptors! How many descriptors? Honestly, a lot. Just look at this quick review of all of them:
* **`system`:** specifies which algorithm will be used to construct the counter’s string representation. (Obligatory)
* **`negative`:** specifies the counter representation if the counter value is negative. (Optional)
* **`prefix`:** specifies a character that will be attached **before** the marker representation and any negative sign. (Optional)
* **`suffix`:** specifies a character that will be attached **after** the marker representation and any negative sign. (Optional)
* **`range`:** specifies the range in which the custom counter is used. Counter values outside the range will drop to their `fallback` counter style. (Optional)
* **`pad`:** specifies a minimum width all representations have to reach. Representations shorter than the minimum are padded with a character. (Optional)
* **`fallback`:** specifies a fallback counter used whenever a counter style can’t represent a counter value. (Optional)
* **`symbols`:** specifies the symbols used by the construction `system` algorithm. It’s obligatory unless the `system` is set to `additive` or `extends`.
* **`additive-symbols`:** specifies the symbols used by the construction algorithm when the `system` descriptor is set to `additive`.
* **`speak-as`:** specifies how screen readers should read the counter style. (Optional)
However, I’ll focus on the required descriptors first: `system`, `symbols` and `additive-symbols`.
### The `system` descriptor
The `symbols` or `additive-symbols` descriptors define the characters used for the counter style, while `system` says how to use them.
The valid `system` values are:
* `cyclic`
* `alphabetic`
* `symbolic`
* `additive`
* `fixed`
* `extends`
**`cyclic`** will go through the characters set on `symbols` and repeat them. We can use just one character in the `symbols` to mimic a bullet list:
```
@counter-style cyclic-example {
system: cyclic;
symbols: "⏵";
suffix: " ";
}
```
* bread
* butter
* milk
* apples
Or alternate between two or more characters:
```
@counter-style cyclic-example {
system: cyclic;
symbols: "🔸" "🔹";
suffix: " ";
}
```

**`fixed`** will write the characters in `symbols` descriptor just one time. In the last example, only the first two items will have a custom counter if set to `fixed`, while the others will drop to their `fallback`, which is decimal by default.
```
@counter-style multiple-example {
system: fixed;
symbols: "🔸" "🔹";
suffix: " ";
}
```

We can set when the custom counters start by appending an `` to the `fixed` value. For example, the following custom counter will start at the fourth item:
```
@counter-style fixed-example {
system: fixed 4;
symbols: "💠";
suffix: " ";
}
```

**`numeric`** will numerate list items using a **custom positional system** (base-2, base-8, base-16, etc.). Positional systems start at 0, so the first character at `symbols` will be used as 0, the next as 1, and so on. Knowing this, we can make an ordered list using non-decimal numerical systems like hexadecimal:
```
@counter-style numeric-example {
system: numeric;
symbols: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F";
suffix: ". ";
}
```
1. bread
2. butter
3. milk
4. apples
**`alphabetic`** will enumerate the list items using a custom alphabetical system. It’s similar to the numeric system but with the key difference that it doesn’t have a character for 0, so the next digits are just repeated. For example, if our `symbols` are `"A" "B" "C"` they will wrap to `"AA", "AB", "AC"`, then `BA, BB, BC` and so on.
Since there is no equivalent for 0 and negative values, they will drop down to their fallback.
```
@counter-style alphabetic-example {
system: alphabetic;
symbols: "A" "B" "C";
suffix: ". ";
}
```
1. bread
2. butter
3. milk
4. apples
5. cinnamon
**`symbolic`** will go through the characters in `symbols` repeating them one more time each iteration. So for example, if our `symbols` are `"A", "B", "C"`, it will go “A”, “B”, and “C”, double them in the next iteration as “AA”, “BB”, and “CC”, then triple them as “AAA”, “BBB”, “CCC” and so on.
Since there is no equivalent for 0 and negative values, they will drop down to their fallback.
```
@counter-style symbolic-example {
system: symbolic;
symbols: "A" "B" "C";
suffix: ". ";
}
```
1. bread
2. butter
3. milk
4. apples
5. cinnamon
**`additive`** will give characters a numerical value and add them together to get the counter representation. You can think of it as the way we usually count bills: if we have only $5, $2, and $1 bills, we will add them together to get the desired quantity, trying to keep the number of bills used at a minimum. So to represent 10, we will use two $5 bills instead of ten $1 bills.
Since there is no equivalent for negative values, they will drop down to their fallback.
```
@counter-style additive -example {
system: additive;
additive-symbols: 5 "5️⃣", 2 "2️⃣", 1 "1️⃣";
suffix: " ";
}
```

Notice how we use `additive-symbols` when the system is `additive`, while we use just `symbols` for the previous systems.
**`extends`** will create a custom style from another one but with modifications. To do so, it takes a `` after the `extends` value. For example, we could change the `decimal` counter style default’s `suffix` to a closing parenthesis (`")"`)\`:
```
@counter-style extends-example {
system: extends decimal;
suffix: ") ";
}
```
1. bread
2. butter
3. milk
4. cinnamon
Per spec, “If a `@counter-style` uses the `extends` system, it must not contain a `symbols` or `additive-symbols` descriptor, or else the `@counter-style` rule is invalid.”
### The other descriptors
The **`negative`** descriptor allows us to create a custom representation for a list’s negative values. It can take one or two characters: The first one is prepended to the counter, and by default it’s the _hyphen-minus_ (`"-"`). The second one is appended to the symbol. For example, we could enclose negative representations into parenthesis `(2), (1), 0, 1, 2`:
```
@counter-style negative-example {
system: extends decimal;
negative: "(" ")";
}
```
1. bread
2. butter
3. milk
4. apples
The **`prefix`** and **`suffix`** descriptors allow us to prepend and append, respectively, a character to the counter representation. We can use it to add a character at the beginning of each counter using `prefix`:
```
@counter-style prefix-suffix-example {
system: extends decimal;
prefix: "(";
suffix: ") ";
}
```
* bread
* butter
* milk
* apples
The **`range`** descriptor defines **an inclusive** range in which the counter style is used. We can define a bounded range by writing one `` next to another. For example, a range of 2 4 will affect elements 2, 3, and 4:
```
@counter-style range-example {
system: cyclic;
symbols: "‣";
suffix: " ";
range: 2 4;
}
```
* bread
* butter
* milk
* apples
* cinnamon
On the other hand, using the `infinite` value we can unbound the range to one side. For example, we could write `infinite `3 so all items up to 3 have a counter style:
```
@counter-style range-example {
system: alphabetic;
symbols: "A" "B" "C";
suffix: ". ";
range: infinite 3;
}
```
* bread
* butter
* milk
* apples
* cinnamon
The `pad` descriptor takes an `` that represents the minimum width for the counter and a character to pad it. For example, a zero-padded counter style would look like the following:
```
@counter-style pad-example {
system: extends decimal;
pad: 3 "0";
}
```
* bread
* butter
* milk
* apples
The **`fallback`** descriptor allows you to define which counter style should be used as a fallback whenever we can’t represent a specific count. For example, the following counter style is `fixed` and will fallback to `lower-roman` after the sixth item:
```
@counter-style fallback-example {
system: fixed;
symbols: "⚀" "⚁" "⚂" "⚃";
fallback: lower-roman;
}
```
* bread
* butter
* milk
* apples
* cinnamon
Lastly, the `speak-as` descriptor hints to speech readers on how the counter style should be read. It can be:
* `auto` Uses the `system` default.
* `bullets` reads an unordered list. By default, `cyclic` systems are read as `bullets`
* `numbers` reads the counter’s numeric value in the content language. By default, `additive`, `fixed`, `numeric`, and, `symbolic` are read as `numbers`.
* `words` reads the counter representation as words.
* `spell-out` reads the counter representation letter by letter. By default, `alphabetic` is read as `spell-out`.
* `` It will use that counter’s `speak-as` value.
```
@counter-style speak-as-example {
system: extends decimal;
prefix: "Item ";
suffix: " is ";
speak-as: words;
}
```
### `symbols()`
The `symbols()` function defines an only-use counter style without the need to do a whole [@counter-style](https://css-tricks.com/almanac/rules/c/counter-style/), but at the cost of missing some features. It can be used inside the [list-style-type](https://css-tricks.com/list-style-recipes/) property and the `counter()` and `counters()` functions.
```
ol {
list-style-type: symbols(cyclic "🥬");
}
```
However, its browser support is appalling since it’s only supported in Firefox.
## Images in Counters
In theory, there are four ways to add images to lists:
1. `list-style-image` property
2. `content` property
3. `symbols` descriptor in `@counter-style`
4. `symbols()` function.
In practice, the only supported ways are using `list-style-image` and `content`, since support for images in `@counter-style` and support in general for `symbols()` _isn’t the best_ (it’s pretty bad).
### `list-style-image`
The `list-style-image` can take an image or a gradient. In this case, we want to focus on images but gradients can also be used to create custom square bullets:
```
li {
list-style-image: conic-gradient(red, yellow, lime, aqua, blue, magenta, red);
}
```
* bread
* butter
* milk
* apples
Sadly, changing the shape would require styling more the `::marker` and this isn’t currently possible.
To use an image, we pass its `url()`, make sure is small enough to work as a counter:
```
li {
list-style-image: url("./logo.svg");
}
```
* bread
* milk
* butter
* apples
### `content`
The `content` property works similar to `list-style-image`: we pass the image’s `url()` and provide a little padding on the left as an empty string:
```
li::marker {
content: url("./logo.svg") " ";
}
```

## Spacing Things Out
You may notice in the last part how the image — depending on its size — isn’t completely centered on the text, and also that we provide an empty string on `content` properties for spacing instead of giving things either a `padding` or `margin`. Well, there’s an explanation for all of this, as since spacing is one of the biggest pain points when it comes to styling lists.
### Margins and paddings are _wacky_
Spacing the `::marker` from the list item should be as easy as increasing the marker’s or list `margin`, but in reality, it takes a lot more work.
First, the `padding` and `margin` properties aren’t allowed in `::marker`. While lists have two types of elements: the list wrapper (usually `ol` or `ul`) and the list item (`li`), each with a default `padding` and `margin`. Which should we use?
You can test each property in this demo by Šime Vidas in his article dedicated to [the gap after the list marker](https://css-tricks.com/everything-you-need-to-know-about-the-gap-after-the-list-marker/):
CodePen Embed Fallback
You’ll notice how the only property that affects the spacing within `::marker` and the text is the `li` item’s `padding` property, while the rest of the spacing properties will move the entire list item. Another thing to note is even when the `padding` is set to `0px`, there is a space after the `::marker`. This is set by browsers and will vary depending on which browser you’re using.
### `list-style-position`
One last thing you may notice in the demo is a checkbox for the `list-style-position` property, and how once you set it to `inside`, the `::marker` will move to the inside of the box, at the cost of removing any spacing given by the list item’s padding.
By default, markers are rendered _outside_ the `ul` element’s box. A lot of times, this isn’t the best behavior: markers sneak out of elements, `text-align` won’t align the marker, and paradoxically, centered lists with `flex` or `grid` won’t look completely centered since the markers are outside the box.
To change this we can use the `list-style-position` property, it can be either `outside` (default) and `inside` to define where to position the list marker: either outside or the outside of the `ul` box.
```
ul {
border: solid 2px red;
}
.inside {
list-style-position: inside;
}
.outside {
list-style-position: outside;
}
```
* bread
* butter
* milk
* apple
### `content` with empty strings
In the same article, Šime says:
> Appending a space to `content` feels more like a workaround than the optimal solution.
And I completely agree that’s true, but just using `::marker` there isn’t a _correct_ way to add spacing between the `::marker` and the list text, especially since most people prefer to set `list-style-position` to `inside`. So, as much as it pains me to say it, the simplest way to increase the gap after the marker is to suffix the content property with an empty string:
```
li::marker {
content: "• ";
}
```
* bread
* milk
* butter
* apples
BUT! This is only if we want to be purists and stick with the `::marker` pseudo-element because, in reality, there is a much better way to position that marker: **not using it at all**.
### Just use `::before`
There is a reason people love using the `::before` more than `::marker`. First, we can’t use something like CSS Grid or Flexbox since changing the display of `li` to something other than `list-item` will remove the `::marker`, and we can set the `::marker`‘s height or width properties to better align it.
Let’s be real, `::marker` works fine when we just want simple styling. But we are not here for simple styling! Once we want something more involved, `::marker` will fall short and we’ll have to use the `::before` pseudo-element.
Using `::before` means we can use Flexbox, which allows for two things we couldn’t do before:
* Vertically center the marker with the text
* Easily increase the gap after the marker
Both can be achieved with Flexbox:
```
li {
display: flex;
align-items: center; /* Vertically center the marker */
gap: 20px; /* Increases the gap */
list-style-type: none;
}
```
The original `::marker` is removed by changing the `display`.
## Accesibility
In a previous section we turned things that weren’t lists into seemingly looking lists, so the question arises: _should_ we actually do that? Doesn’t it hurt accessibility to make something look like a list when it isn’t one? As always, _it depends_. For a visual user, all the examples in this entry look all right, but for assistive technology users, some examples lack the necessary markup for accessible navigation.
Take for example our initial demo. Here, listing titles serves as decoration since the markup structure is given by the titles themselves. It’s the same deal for the counting siblings demo from earlier, as assistive technology users can read the document through the title structure.
However, this is the exception rather than the norm. That means a couple of the examples we looked at would fail if we need the list to be announced as a list in assistive technology, like screen readers. For example this list we looked at earlier:
```
The Old Buccaneer
The Sea Cook
My Shore Adventure
The Log Cabin
My Sea Adventure
Captain Silver
```
…should be written as a list instead:
```
- The Old Buccaneer
- The Sea Cook
- My Shore Adventure
- The Log Cabin
- My Sea Adventure
- Captain Silver
```
Listing elements is rarely used just as decoration, so as a rule of thumb, use lists in the markup even if you are planning to change them with CSS.
## Almanac References
### List Properties
**Almanac** on Apr 23, 2021
### [ list-style ](https://css-tricks.com/almanac/properties/l/list-style/)
[ ul { list-style: square outside none; } ](https://css-tricks.com/almanac/properties/l/list-style/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/saracope/) [ Sara Cope ](https://css-tricks.com/author/saracope/)
### Counters
**Almanac** on Feb 4, 2025
### [ counter-reset ](https://css-tricks.com/almanac/properties/c/counter-reset/)
[ article { counter-reset: section; } ](https://css-tricks.com/almanac/properties/c/counter-reset/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/saracope/) [ Sara Cope ](https://css-tricks.com/author/saracope/)
**Almanac** on Jan 14, 2025
### [ counter-increment ](https://css-tricks.com/almanac/properties/c/counter-increment/)
[ .step { counter-increment: my-awesome-counter; } ](https://css-tricks.com/almanac/properties/c/counter-increment/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/saracope/) [ Sara Cope ](https://css-tricks.com/author/saracope/)
**Almanac** on Apr 23, 2021
### [ counter-set ](https://css-tricks.com/almanac/properties/c/counter-set/)
[ h2:first-of-type::before { counter-set: chapter; } ](https://css-tricks.com/almanac/properties/c/counter-set/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/geoffgraham/) [ Geoff Graham ](https://css-tricks.com/author/geoffgraham/)
**Almanac** on Feb 4, 2025
### [ counter() ](https://css-tricks.com/almanac/functions/c/counter/)
[ h2::before { content: counter(my-counter, upper-roman) ". "; } ](https://css-tricks.com/almanac/functions/c/counter/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[
](https://css-tricks.com/author/monknow/) [ Juan Diego Rodríguez ](https://css-tricks.com/author/monknow/)
**Almanac** on Feb 4, 2025
### [ counters() ](https://css-tricks.com/almanac/functions/c/counters/)
[ li::marker { content: counters(item, ".") ") "; } ](https://css-tricks.com/almanac/functions/c/counters/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[
](https://css-tricks.com/author/monknow/) [ Juan Diego Rodríguez ](https://css-tricks.com/author/monknow/)
### Custom Counter Styles
**Almanac** on Jan 28, 2025
### [ @counter-style ](https://css-tricks.com/almanac/rules/c/counter-style/)
[ @counter-style apple-counter { ... } ](https://css-tricks.com/almanac/rules/c/counter-style/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[
](https://css-tricks.com/author/monknow/) [ Juan Diego Rodríguez ](https://css-tricks.com/author/monknow/)
**Almanac** on Jan 30, 2025
### [ symbols() ](https://css-tricks.com/almanac/functions/s/symbols/)
[ ul { list-style: symbols(cyclic "🥬"); } ](https://css-tricks.com/almanac/functions/s/symbols/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[
](https://css-tricks.com/author/monknow/) [ Juan Diego Rodríguez ](https://css-tricks.com/author/monknow/)
### Pseudo-Elements
**Almanac** on Jan 19, 2025
### [ ::marker ](https://css-tricks.com/almanac/pseudo-selectors/m/marker/)
[ li::marker { color: red; } ](https://css-tricks.com/almanac/pseudo-selectors/m/marker/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/geoffgraham/) [ Geoff Graham ](https://css-tricks.com/author/geoffgraham/)
**Almanac** on Sep 13, 2024
### [ ::before / ::after ](https://css-tricks.com/almanac/pseudo-selectors/b/after-and-before/)
[ .element::before { content: "Yo!"; } ](https://css-tricks.com/almanac/pseudo-selectors/b/after-and-before/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/saracope/) [ Sara Cope ](https://css-tricks.com/author/saracope/)
## More Tutorials & Tricks!
**Article** on May 5, 2020
### [ List Style Recipes ](https://css-tricks.com/list-style-recipes/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/chriscoyier/) [ Chris Coyier ](https://css-tricks.com/author/chriscoyier/)
**Article** on Apr 29, 2021
### [ List Markers and String Styles ](https://css-tricks.com/list-markers-and-string-styles/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[
](https://css-tricks.com/author/ericmeyer/) [ Eric Meyer ](https://css-tricks.com/author/ericmeyer/)
**Article** on May 19, 2018
### [ Style List Markers in CSS ](https://css-tricks.com/style-list-markers-css/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/chriscoyier/) [ Chris Coyier ](https://css-tricks.com/author/chriscoyier/)
**Article** on Jun 11, 2020
### [ How to Reverse CSS Custom Counters ](https://css-tricks.com/how-to-reverse-css-custom-counters/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/etinosaobaseki/) [ Etinosa Obaseki ](https://css-tricks.com/author/etinosaobaseki/)
**Article** on Jan 23, 2025
### [ Some Things You Might Not Know About Custom Counter Styles ](https://css-tricks.com/some-things-you-might-not-know-about-custom-counter-styles/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/geoffgraham/) [ Geoff Graham ](https://css-tricks.com/author/geoffgraham/)
**Article** on Jan 26, 2022
### [ Using CSS Counters for Custom List Number Styling ](https://css-tricks.com/css-counters-custom-list-number-styling/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/chriscoyier/) [ Chris Coyier ](https://css-tricks.com/author/chriscoyier/)
**Article** on May 17, 2024
### [ Everything You Need to Know About the Gap After the List Marker ](https://css-tricks.com/everything-you-need-to-know-about-the-gap-after-the-list-marker/)
[counters](https://css-tricks.com/tag/counters/) [lists](https://css-tricks.com/tag/lists/)
[ ](https://css-tricks.com/author/simevidas/) [ Šime Vidas ](https://css-tricks.com/author/simevidas/)
---
[Styling Counters in CSS](https://css-tricks.com/styling-counters-in-css/) originally published on [CSS-Tricks](https://css-tricks.com), which is part of the [DigitalOcean](https://try.digitalocean.com/css-tricks/?utm%5Fmedium=rss&utm%5Fsource=css-tricks.com&utm%5Fcampaign=family%5F&utm%5Fcontent=) family. You should [get the newsletter](https://css-tricks.com/newsletters/).
https://css-tricks.com/styling-counters-in-css/