Pokémon as HTML list bullets using SASS functions
Tags: sass, css, html, pokémon
Published: 2022-07-25 18:21:49

---
title: "Pokémon as HTML list bullets using SASS functions"
published: true
description: How to use numbered image URLs as the bullets of an HTML ordered list.
tags: SASS, CSS, HTML, Pokémon
canonical_url: https://aileenrae.co.uk/blog/pokemon-as-html-list-bullets
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yw39s9wlofcxkfpg6b7j.png
---

ℹ _This is a chatty post describing my step-by-step development process. You can [skip to the end result here](#the-result)._

Earlier this year, one of [CodePen](https://codepen.io/)'s weekly challenges was all about [list styles](https://codepen.io/challenges/2022/april/1). If you have not already, I highly recommend [subscribing to Codepen's weekly challenges](https://codepen.io/challenges). They're a great inspiration for little frontend dev exercises.

![Screenshot of the Codepen List Styles Challenge page.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skw6b2uaexctqutx3cyk.png)

Back to list styles - I was immediately feeling inspired to code up something frivolous and fun. I decided I wanted to replace the traditional numbers of an [ordered list](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol) with images that esoterically represent numbers. Specifically, I decided to use Pokémon in [Pokédex order](https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_National_Pok%C3%A9dex_number).

Yes, I am a Pokémon nerd, but so are a lot of developers in the community. There are a lot of great Pokémon resources for dev side projects such as [PokéAPI](https://pokeapi.co/). For this little project, I chose [PokéSprite](https://msikma.github.io/pokesprite/) sprite images to be my ordered list bullets.

![Screenshot of the Codepen List Styles Challenge page.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gtiwixgwzg79v0cf3c2q.png)

## How I built it

### Step 1: Incrementing numbers in CSS rules

I started thinking abstractly about what I needed the CSS to do. Replacing a single list number marker with an image would require the following CSS:

```css
ol li::marker {
  content: url("image_url_here");
}
```

In order to have a different image for each list item, I needed a different rule for each child using the [nth-of-type CSS pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type).

```css
ol li:nth-of-type(1)::marker {
  content: url("image_001");
}

ol li:nth-of-type(2)::marker {
  content: url("image_002");
}

ol li:nth-of-type(3)::marker {
  content: url("image_003");
}

/* etc etc */
```

Copy-pasting this for each list item would quickly become tedious. There are now [nearly 1,000 Pokémon](https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_National_Pok%C3%A9dex_number). No way: I wanted a programmatic solution.

At this point I wanted to style this with vanilla CSS if possible, so I began playing with the [counter() CSS function](https://developer.mozilla.org/en-US/docs/Web/CSS/counter) to programatically generate the numbers. Something like this:

```css
counter-increment: idx;

ol li:nth-of-type(idx)::marker {
  content: url("image_{idx}"); /* This is invalid 😡 */
}
```

But, after several attempts and furious googling on how to put the counter value in an image URL, I found [this (unecessarily downvoted) Stack Overflow question](https://stackoverflow.com/questions/68157908/can-i-insert-css-counter-in-content-url-of-pseudo-before-element):

> Can I insert css counter in content url?

The answer is **no**. Bummer. 😞

On the plus side, the answer to that Stack Overflow question provided the solution: a [SASS @for loop](https://sass-lang.com/documentation/at-rules/control/for).

With that I had the CSS plumbing ready for my Pokémon sprite image URLs:

```scss
ol li {
  @for $i from 1 through 908 {
    &:nth-of-type(#{$i})::marker {
      content: url("#{$i}.png");
    }
  }
}
```

### Step 2: Preparing the image files

Now the only thing missing was the Pokémon image files themselves. I downloaded [this collection of sprites](https://github.com/msikma/pokesprite/tree/master/pokemon-gen7x/regular) from PokéSprites and quickly noticed I had made a breaking assumption: the files were named after the Pokémon rather than their dex number, e.g. `bulbasaur.png`.

Not to worry though, PokéSprite also comes with [a structured JSON data file](https://github.com/msikma/pokesprite/blob/master/data/pokemon.json) through which I could map the filename to the dex number. I got cracking on writing a script I could run to rename the hundreds of files for me. I'm not a Node expert so after a lot of googling about Node FS, I ended up with this:

```js
// extractOrderedListOfPokemon.mjs
import * as input from './pokemon.json';
import * as fs from 'fs';

const dirPath = './by-nat-dex-number';
const data = input.default;
const filesToDelete = [];

const errCallback = err => {
  if (err) {
    console.error('ERROR: ' + err);
  }
}

const dir = await fs.promises.opendir(dirPath);
for await (const dirent of dir) {
  const filename = dirent.name;
  const pokemonSlug = filename.replace(".png", "");
  const dexNumber = Object.values(data).find(pokemon => pokemon.slug.eng === pokemonSlug)?.idx;

  if (dexNumber) {
    fs.rename(`${dirPath}/${filename}`, `${dirPath}/${dexNumber}.png`, errCallback);
  } else {
    filesToDelete.push(`${dirPath}/${filename}`);
  }
}

filesToDelete.forEach(file => {
  fs.unlink(file, errCallback);
});
```

I ran this as a node script in my terminal. After learning I needed the `--experimental-json-modules` option for the `.json` import to work, I had the files I needed named as `001.png`, `002.png` etc, all the way up to `908.png`. I uploaded them all to an AWS S3 bucket for hosting.

### Step 3: A SASS zerofill function

All I thought was left to do was to plug my image URLs into my code like so:

```scss
ol li {
  @for $i from 1 through 908 {
    &:nth-of-type(#{$i})::marker {
      content: url("https://my-s3-bucket-domain.com/#{$i}.png");
    }
  }
}
```

except:

![An HTML list with Pokémon icons as bullets, but there are no images before the 100th list item.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6c04i25v9is426ctah8z.png)

Oops, my image urls are _zerofilled_, or padded with zeroes at the start so that each filename is the same length of characters. My CSS code is looking for `1.png` when it needs `001.png`.

Once again, [Stack Overflow had my solution](https://stackoverflow.com/a/20122879/16315892), and I adapted my code to:

```scss
@function zerofill($i) {
  @return #{str-slice("000", 0, 3 - str-length(#{$i}))}#{$i};
}

ol li {
  @for $i from 1 through 908 {
    &:nth-of-type(#{$i})::marker {
      $i: zerofill($i);
      content: url("https://my-s3-bucket-domain.com/#{$i}.png");
    }
  }
}
```

### The result

And ta-da, 🎉 my Pokémon ordered list!

{% codepen https://codepen.io/aileen-r/pen/zYpjLyg %}

⚠ If you're a Safari or iOS user, unfortunately you won't see the Pokémon bullets. At the time of writing, Safari has limited support for the `::marker` pseudo-element. You can check out [the bug ticket](https://bugs.webkit.org/show_bug.cgi?id=204163) for more details.