By Matt Visiwig May 5, 2023
There are two established ways to create custom HTML bullets via CSS.
We’ll talk about the CSS ::marker technique, which is easy to implement, but limited in customization options. If you need more precision and control, look into the advanced method which declares list-style: none;
to override the default bullet behavior.
It’s easy to add your custom bullet, declare list-style-image:
with your image and you’re 80% done. The challenge is making adjustments if your custom bullet doesn’t fit or align quite right, but before we go there, let’s demo the first steps.
First, let’s make sure you’re setup with an HTML list, with some <li>
(list item) elements wrapped in an <ul>
(unordered list).
<ul>
<li>Jupiter</li>
<li>Neptune</li>
<li>Mars</li>
</ul>
The browser renders that unordered list like so:
Even if you don’t work in HTML, your WYSYWIG editor likely outputs code exactly like this. I use WordPress, where both the old TinyMCE and new Gutenberg block editor outputs this markup.
Next we need the image we want as the custom bullet. SVG icons make great bullets, so I’ll grab an SVG from our List Item collection, which has some free SVGs. I export as CSS, but if you end up with SVG code you can run it through a SVG to CSS converter to prepare for the next step.
Next we declare the image as the HTML bullet with CSS. I’m placing the rule on the li element, but if you want to apply this to a specific item or list, you can get more specific with a class or so.
li{
list-style-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23808"><path d="M18 4c-4-1-6 3-6 3s-2-4-6-3-4 6-2 8l8 8 8-8c2-2 2-7-2-8z"></path></svg>');
}
The code between the url(
and );
is known as a Data URI, which I prefer to use because it means I don’t have to upload and link to any image files. You could use url('image-name.svg');
if you prefer to work with files. But this all that is needed to achieve the result below:
Sweet, we got hearts instead of the default black dots, aka disc bullets. But they’re kinda small, what if we wanted them bigger?
I mentioned in the beginning, with a single CSS property, we’re achieved 80% of the results were after. However, the last 20% is the much tougher to get just right. We are limited in CSS properties that can affect the size and position of these custom bullets.
This first way tool to manipulate size is by changing the font-size. We can apply one size to the list test and another size to the bullet with li::marker
. If the size is bigger, you may need to also add a smaller line-height sizing to the li::marker
to counter the larger vertical space added to the <li>
element.
li::marker{
font-size: 1.8em;
line-height: 0.1;
}
That helps us match the image size to the text, but the alignment seems off to me.
If we made the font-size 1.4em
, I could live with the results, but this tutorial wouldn’t be as useful. With the bigger size, it’s obvious that the positioning over the <li>
element is off.
Most of the usual positioning approaches fail because the ::marker
only accepts a few specific properties. This means you can’t use transform: translate(X, Y)
, vertical-align
, margin
, padding
, nor background-position
. In fact, there doesn’t seem to be any ::marker
property that shifts strictly the alignment, and any shift on the <li>
element, shifts the ::marker
too.
The workaround is not ideal, because it requires extra markup.
If we wrap a <span>
around the <li>
, you can apply a transform or vertical-align that will shift the content in relation to the <li> element’s position. This is the shift in alignment we want, here’s what that code might look like:
li span{
vertical-align: 0.1em;
transform: translate(-0.1em, 0.2em);
}
You don’t need both properties, but you certainly can use either.
If you want to avoid the extra markup, I got good news and bad news: It’s possible, but it requires manipulating the SVG.
If you’ve been following along, we grabbed an SVG icon that we didn’t make ourselves. I wouldn’t expect you to fire up Illustrator, make the tweaks, and re-export the SVG code — and repeat a few times to get it right.
There has to be a better way, right?!
You could tweak the viewBox, after all most icon systems are built with extra padding. Shrinking or enlarging the viewBox by 1 or 2 units often is enough to achieve the desired shift, but those unintuitive changes alter the core instructions of the SVG. Here’s what changing the viewBox would look in case it might help someone out there:
<!-- original viewBox -->
<svg viewBox="0 0 24 24"></svg>
<!-- one unit bigger above and below -->
<svg viewBox="0 -1 24 26"></svg>
<!-- two units smaller above, 1 below -->
<svg viewBox="0 2 24 21"></svg>
The most straightforward way to shift elements is a transform
with a translate(X, Y)
. Simply wrap the inner SVG elements with a <g>
group element and place a transform=""
on it.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g transform="translate(0, 10)">
<path d="M18 4c-4-1-6 3-6 3s-2-4-6-3-4 6-2 8l8 8 8-8c2-2 2-7-2-8z"></path>
</g>
</svg>
This works, but you’ll notice shifting the content too much pushes it outside the viewBox, making it clipped. With a few extra properties, we can counter this negative effect. First we’ll want to scale the SVG smaller and you can do that by adding scale(X, Y)
to the transform. You’ll want a number between 0 and 1 to shrink it, where a single value will represent both the X-scale
and Y-scale
.
By default the SVG scales to the top left. I’m more accustomed to scaling from the center, which is why I added the transform-origin="center"
to the <g>
.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g transform="scale(0.5) translate(0, 10)" transform-origin="center">
<path d="M18 4c-4-1-6 3-6 3s-2-4-6-3-4 6-2 8l8 8 8-8c2-2 2-7-2-8z"></path>
</g>
</svg>
Now that we scaled down the SVG, there is more room to shift the SVG content. But there is a new problem, it’s small. Well we already learned how to change the bullet size with font-size
on the ::marker
, so we can adjust that as needed to compensate.
All those tricks together help us tweak the custom SVG bullet as desired. Here is the CSS in it’s entirety. The magic is hidden in the SVG code within the list-style-image
property.
li{
font-size: 1.2em;
list-style-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23808"><g transform="scale(0.5) translate(0, 15)" transform-origin="center"><path d="M18 4c-4-1-6 3-6 3s-2-4-6-3-4 6-2 8l8 8 8-8c2-2 2-7-2-8z"></path></g></svg>');
}
li::marker{
font-size: 3.6em;
line-height: .1;
}
Here are the results:
Hey, I'm Matt , the creator behind SVG Backgrounds. I produce free and paid resources every month, sign up for alerts.