Creative Ways to Style Lists with CSS
Plain browser-default lists rarely match a real design. Whether you’re building a navigation menu, a step-by-step tutorial, or a product feature list, CSS list styling gives you the tools to make them look intentional. This article covers the most practical, standards-based techniques—from native list properties to CSS counters and custom markers—without sacrificing accessibility.
Key Takeaways
- Choose the correct semantic HTML list element (
<ul>,<ol>, or<dl>) before applying any CSS, as screen readers rely on this structure. - Use
::markerfor simple marker color and font changes, and::beforewith flexbox when you need full layout control over custom bullets. - CSS counters let you create custom numbered formats, including multi-level hierarchical numbering with
counters(). - Consider adding
role="list"when usinglist-style: noneon meaningful lists, especially to preserve list semantics for Safari/VoiceOver.
Start with the Right HTML List Type
Before touching CSS, pick the right element:
<ul>— unordered lists where sequence doesn’t matter (navigation menus, feature lists)<ol>— ordered lists where sequence is meaningful (instructions, rankings)<dl>— description lists pairing terms with definitions (glossaries, metadata)
Semantic HTML matters here. Screen readers announce list type and item count, which helps users understand context before they read a single item.
The list-style-* Properties: Your Starting Point
The list-style shorthand bundles three properties:
ul {
list-style: square inside none;
/* list-style-type | list-style-position | list-style-image */
}
list-style-position is worth understanding clearly:
outside(default) — the marker sits in the margin, and text stays neatly alignedinside— the marker flows with text, causing wrapping on multi-line items
list-style-image exists but is limited—you can’t resize or reposition the image. For custom image bullets, a ::before pseudo-element with background-image gives you far more control.
Styling Native Markers with CSS ::marker
The ::marker pseudo-element lets you style the built-in bullet or number directly—no extra markup required:
li::marker {
color: deeppink;
font-size: 1.2em;
font-weight: bold;
}
Important limitation: ::marker only supports a specific subset of CSS properties: color, content, font-*, direction, unicode-bidi, white-space, text-combine-upright, and animation/transition properties. You cannot apply display, background, padding, margin, or positioning. Treat it as a text-level styling hook, not a full element.
Browser support for ::marker is good in current browsers, but check your target browser matrix. It is usually the cleanest option for simple color or font changes to native markers.
Custom List Bullets with ::before
When ::marker isn’t enough—for icon-style bullets, complex alignment, or transitions—use ::before instead:
ul {
list-style: none;
padding-left: 0;
}
ul li {
display: flex;
align-items: flex-start;
gap: 0.5rem;
}
ul li::before {
content: "✓";
color: green;
flex-shrink: 0;
}
Accessibility note: Setting
list-style: nonecan cause Safari/VoiceOver to stop exposing the element as a list. If the list semantics are meaningful, addrole="list"to the<ul>or<ol>.
Discover how at OpenReplay.com.
CSS Counters for Styled Ordered Lists
CSS counters give you full control over numbered list formatting—useful for styled ordered lists with custom prefixes, suffixes, or multi-level numbering:
ol {
list-style: none;
counter-reset: steps;
}
ol li {
counter-increment: steps;
}
ol li::before {
content: "Step " counter(steps) ". ";
font-weight: bold;
color: #333;
}
Nested Counters
For nested lists, counters() (plural) outputs the full hierarchy:
ol {
list-style: none;
counter-reset: steps;
}
ol li {
counter-increment: steps;
}
ol li::before {
content: counters(steps, ".") " ";
}
/* Outputs: 1, 1.1, 1.2, 2, 2.1 */
The counter-increment property also accepts a step size: counter-increment: steps 2 increments by two each time.
A Note on @counter-style
The @counter-style at-rule lets you define entirely custom counting systems—custom symbols, alphabets, or cyclic patterns:
@counter-style thumbs {
system: cyclic;
symbols: "👍" "👎";
suffix: " ";
}
ul {
list-style-type: thumbs;
}
Use with caution. Browser support for @counter-style remains incomplete—notably, Safari added support only in version 17. Always test across your target browsers and define a list-style-type fallback on the same element to ensure graceful degradation.
Choosing the Right Approach
| Goal | Best technique |
|---|---|
| Change marker color or font | ::marker |
| Custom icon or complex alignment | ::before with flexbox |
| Custom numbered formatting | CSS counters with ::before |
| Custom counting system | @counter-style (with fallback) |
Conclusion
Good CSS list styling starts with semantic HTML and layers visual improvements on top. Use ::marker for lightweight color and font changes, ::before when you need layout control, and CSS counters when ordered lists need custom numbering. Avoid removing list semantics without restoring them via ARIA. Each technique has a clear use case—pick the one that fits your design without overcomplicating it.
FAQs
Yes, technically, but usually you choose one marker strategy. If you remove the native marker with list-style: none, use ::before for the custom bullet. If you keep the native marker, use ::marker for simple color and font tweaks.
Safari's accessibility heuristics interpret list-style: none as a signal that the element is being used for layout rather than as a semantic list. This causes VoiceOver to stop announcing it as a list. Adding role list to the ul or ol element explicitly restores the list semantics so assistive technologies continue to report the correct structure and item count.
Yes. CSS counters are not restricted to list elements. You can apply counter-reset and counter-increment to any element, such as divs, headings, or sections, and display the counter value using the content property on a pseudo-element. This makes them useful for numbering chapters, figures, or any repeating content pattern.
Set the ul to display flex and remove default list styles with list-style none and padding-left 0. Then style each li or its anchor as an inline flex item with appropriate spacing using gap or margin. Remember to add role list to the ul if you want screen readers to still announce it as a navigation list in Safari.
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..