Most list styling problems come down to the marker, the bullet or number sitting in front of each item. CSS handles it through list-style-type, list-style-position and list-style-image, plus the ::marker pseudo-element, CSS counters and the @counter-style rule.
Front-end developers use these to swap bullets for icons or to number a nested outline as 1.1 and 1.2. The same ul of links can also be turned into a horizontal menu.
The W3C’s CSS Lists and Counters Module Level 3 says that only list items generate a marker box. The box goes in as the item’s first child, ahead of any ::before content.
What Are CSS Lists?
See the Pen
custom ordered list bullets by Dimitri Denis (@dimilo)
on CodePen.
A marker exists because the element computes to display: list-item. According to MDN, only li and summary do that by default, but any element can opt in with that display value. Browsers also add spacing around ul and ol elements, and the list properties cover that part too.
- list-style-type, list-style-position, list-style-image and the list-style shorthand for the marker itself
- ::marker, which styles the bullet or number directly
- counter-reset, counter-increment and the counter() and counters() functions for numbering
- @counter-style for custom markers
ul, ol and li come from HTML, and CSS only decides how the resulting boxes look.
The list-style properties live in CSS Lists and Counters Module Level 3. @counter-style is defined separately in CSS Counter Styles Level 3, and ::marker belongs to CSS Pseudo-Elements Module Level 4.
What Default Styles Do Browsers Apply to Lists?
See the Pen
The Order of Operations Challenge #cpc-counters #codepenchallenge by Gražvydas Žilius (@deracans-the-styleful)
on CodePen.
Browsers indent ul and ol by 40px and add 1em of vertical margin, according to the suggested rendering in the WHATWG HTML Standard. Most CSS frameworks replace those values.
| Source | Left padding | Vertical margin | Marker |
|---|---|---|---|
| Browser default (HTML Standard) | 40px | 1em, 0 when nested | disc, circle, square; decimal for ol |
| Bootstrap Reboot 5.3 | 2rem | 0 top, 1rem bottom, 0 bottom when nested | Unchanged |
| Tailwind Preflight | 0 | 0 | none |
Bullet shapes step down with depth. A ul nested inside any other list gets circle, and a third level gets square.
Bootstrap keeps the markers and swaps the spacing for rem units, as its Reboot documentation describes. Its list-unstyled class strips the marker from immediate children only, so a nested ul keeps its bullets unless it carries the class too.
Tailwind goes the other direction. Its Preflight docs state that ordered and unordered lists ship with no bullets, numbers, margin or padding, and the list-disc utility brings the bullets back.
A reset touches spacing and markers and nothing else. The li keeps display: list-item and ol and ul keep their list-item counter, so numbering still runs when a marker is switched back on.
The HTML Standard’s rendering section has an exception worth knowing. Its quirks-mode rules set list-style-position: inside on li, so a page without a doctype is expected to place markers differently.
Which list-style-type Value Fits Which List?
See the Pen
Decorated Numbered Lists by Dudley Storey (@dudleystorey)
on CodePen.
list-style-type takes a predefined keyword, a string, a name from @counter-style, or none.
Bullets for a ul come from disc (the default), circle and square. An ol has decimal, decimal-leading-zero, lower-alpha, upper-alpha, lower-roman, upper-roman and lower-greek. A quoted string covers a single character, including a Unicode escape such as “\1F44D”, and none removes the marker entirely.
The property follows the list item, not the list type. A ul can take decimal just fine, and an ol can take square.
Marker color comes from the element’s computed color. Some numbering systems need a suitable font installed, MDN notes.
Nested lists catch people out. User-agent stylesheets set list-style-type on ul, ol and menu, so a nested list ignores the value set on its parent. Add list-style-type: inherit to the nested list to carry the value down.
Old HTML type attributes still count. ol type=”a” maps to lower-alpha through a presentational hint, and because those hints carry zero specificity at the author level, any CSS rule overrides them.
MDN lists list-style-type as Baseline widely available, with support across browsers since July 2015.
How Do list-style-position, list-style-image and the Shorthand Work?
list-style-position decides where the marker sits, and list-style-image replaces it with a picture. The list-style shorthand rolls both into one declaration, together with list-style-type.
list-style-position: inside versus outside
The CSS Lists specification defaults the property to outside. The marker box sits beside the principal block box and hangs in the margin, so wrapped lines align with the first line of text.
With inside, the marker becomes the first inline box and wrapped lines flow back under it.
One browser gap matters here. When a block element is the first child of an inside item, MDN says the block drops to the line after the marker.
MDN’s compatibility notes record that Safari, including iOS, keeps the marker on the same line as the block.
After a padding reset, outside markers hang into the left margin and get clipped by an ancestor with overflow: hidden. Switch to inside there.
Using list-style-image
list-style-image takes a url(), an image function such as linear-gradient(), or none. The initial value is none. It applies to list items and is inherited, so it can sit on the ul.
The property gives no control over image size. Tutorial Republic suggests a background-image on li as the workaround, and for anything beyond a tiny icon that is the better tool.
The CSS Lists Level 3 specification adds one wrinkle: list-style-type: none suppresses the marker unless list-style-image holds a valid image.
The list-style shorthand
The shorthand accepts one to three values in any order, according to MDN.
Omitted values return to their initial value, and that’s the trap. list-style: square also sets the position to outside and the image to none (HTML Dog), so a shorthand declared after list-style-position: inside undoes it.
When a type and an image are both given, the type renders if the image fails to load.
keydata shows the expansion: list-style: url(“circle.gif”) none inside equals an image, a type of none and an inside position. For ready-made list style examples built on this pattern, copy and adjust.
How Do You Style List Markers With ::marker?
Styling the marker box directly is what ::marker is for, though it accepts only a short list of CSS properties.
According to MDN, that list covers all font properties, color, content, white-space, direction and unicode-bidi, plus the animation and transition properties. Backgrounds and margins are not applied, according to CSS Portal. The CSS Lists specification says any property can be set on ::marker, but only a defined subset has an effect.
Selectors work as usual. li::marker colors every marker and li:nth-child(2)::marker targets one (web.dev), and the pseudo-element applies to any display: list-item element, summary included.
Change the marker text with content, not list-style-type. When a design needs a background, a transform or layout control, the workaround is list-style: none plus a ::before on each li, at the price of rebuilding the marker by hand.
Sources disagree on support. MDN marks ::marker as limited availability and not Baseline, while CSS Portal and Savvy state that all modern browsers support it. The numbers differ most for Safari, so test every ::marker property you rely on there.
These are the first browser versions to ship ::marker:
- Chrome 86 (web.dev, 2020)
- Edge 86 (web.dev, 2020)
- Firefox 68 (web.dev, 2020)
- Safari 11.1 (web.dev, 2020), but 18.1 as first full support (W3Schools, as of October 2026)
Chrome DevTools inspects and edits ::marker styles like any other pseudo-element, so debugging a stubborn bullet takes one click in the Elements panel.
How Do CSS Counters Number List Items?
See the Pen
CSS Gradient Counter List by Mattia Astorino (@equinusocio)
on CodePen.
counter-reset creates a counter, counter-increment advances it, and counter() or counters() prints it through the content property.
counter-reset can also set a starting value. counter-increment adds to the counter, and a negative value counts down. counter-set writes a value into a counter and creates one only if none exists on the element yet. counters() takes a name, a separator and an optional counter style.
Ordered lists already run on a built-in list-item counter. MDN notes that counter-increment on li changes the step or starting point of native numbering.
To number a nested outline, work through these steps.
- Strip the native marker with ol { list-style: none; }
- Create the counter on every list with ol { counter-reset: section; }, which gives each nested ol its own instance
- Advance it on each item with li { counter-increment: section; }
- Print it with li::before { content: counters(section, “.”) ” “; } to get labels like 1.1, 1.2 and 2.1
Printing the same counter through li::marker content keeps the native marker and skips the first step, with the browser-support caveats that apply to ::marker.
Counters only generate text. A div numbered with counter() looks like a list item but exposes no list semantics.
Gihyo noted in 2023 that generated text from ::before, ::after and ::marker still cannot be selected in browsers, so a copied list loses its numbers.
How Does @counter-style Create Custom Markers?
See the Pen
Custom properties step progress indicator by Michelle Barker (@michellebarker)
on CodePen.
Reach for @counter-style when list-style-type has no keyword for the numbering you need, such as a symbol cycle or a regional alphabet.
A rule carries a name and a set of descriptors. The name then goes straight into list-style-type or into counter(), according to MDN.
The system descriptor decides how counter values map to symbols (cyclic, fixed, symbolic, alphabetic, numeric, additive or extends). symbols holds the strings or images to use, and additive-symbols takes its place in the additive system. suffix puts text after the marker, with prefix as its counterpart. fallback names the style used when a value falls outside the system, decimal by default. range, pad, negative and speak-as cover value limits, padding, negative signs and speech output.
MDN’s own example gets by with a cyclic system, a single thumbs-up symbol and a space as suffix.
The fixed system only covers as many items as it has symbols. Past that point the fallback style takes over, as Mozilla Hacks explained in 2015.
Sources disagree on support here too. Caniuse lists first support in Firefox 33, Chrome and Edge 91, and Safari 17, yet labels every one of them as partial support.
Gihyo reported in September 2023 that Safari 17.0 closed the last gap between engines. The web-features data from web-platform-dx records speak-as as Firefox-only, so the descriptors are the part to test.
Which Technique Fits Which Job?
Use the lightest technique that does the job. A list-style-type keyword comes first, ::marker second, counters or @counter-style when the numbering is the problem, and ::before only as a last resort.
| Technique | Controls | Stops working when |
|---|---|---|
| list-style-type | Marker shape or numbering keyword | The marker needs its own color or size |
| ::marker | Color, font and content of the marker | You need a background, margin or transform |
| Counters | Outline numbering such as 1.1 and 1.2 | Numbers must be selectable text |
| @counter-style | Reusable custom numbering and symbol cycles | A descriptor you rely on lacks support |
| ::before with list-style: none | Backgrounds, transforms and layout on the marker | You are tired of rebuilding spacing by hand |
An emoji bullet shows the point. A string in list-style-type does the job, and a @counter-style rule only adds code.
Most tutorials jump to ::before for custom bullets. Try ::marker with content first, because it keeps the native marker and leaves one less thing to rebuild.
How to Build a Horizontal List or Navigation Menu
See the Pen
Horizontal Ordered List by Morten Brunbjerg Bech (@bechster)
on CodePen.
Set display: flex on the ul, strip the markers and padding, and let gap handle the spacing between items.
Flexbox gives you one row, with gap for spacing and flex-wrap for narrow screens. Inline-block works too, though you have to deal with whitespace gaps between items. Floats are legacy and need a clearfix on the parent, and grid suits equal-width columns.
Inline-block keeps the whitespace that sits between tags in the HTML source. CSS Working Group member Tab Atkins Jr. noted on the www-style list on 31 December 2009 that floats and flexbox ignore it.
A navigation menu is still a list of links, so keep the ul and li in the markup. Stripping the markers in that step is exactly what triggers the Safari behavior covered next.
Bootstrap’s list-inline and list-inline-item classes do the same strip-and-space job with two classes, per its documentation.
When items need equal-width columns instead of a single row, grid fits better, and a CSS grid generator writes the template syntax for you.
When Does list-style: none Remove List Semantics?
Only in Safari with VoiceOver, and WebKit treats the behavior as intentional.
WebKit’s James Craig explained the rule in a 2018 Bugzilla reply. It separates layout lists from data lists, the way WebKit separates layout tables from data tables. His advice for overriding it was an explicit role=”list”.
- In March 2017, bug 170179 reports that VoiceOver stops announcing a navigation list once list-style is none
- In January 2019, Scott O’Hara shows that any CSS removing the bullets or numbers triggers it on macOS and iOS, while Chrome with VoiceOver is unaffected
- In December 2025, bug 304137 reports iOS VoiceOver reading such a list as plain text while macOS announces it
- In February 2026, WebKit closes 304137 as a duplicate of bug 122320, the layout-list heuristic
When a list is recognized, VoiceOver announces list start, item X of Y and list end, per the WebKit report. When it is not, the items read as plain lines.
Sources disagree on how far the problem reaches. cssence reproduced the bug in Safari 15 in 2022, while a W3C WAI mailing list test the same year found item counts announced from Safari 14.1 onward on macOS.
The December 2025 WebKit report sides with the second result for macOS and adds that iOS still drops the list. MDN’s list-style reference lists an exception for lists nested inside nav, so test your own markup in Safari before deciding.
Each fix comes with a cost.
- role=”list” is one attribute, but validators flag it as redundant
- A zero-width space in li::before needs no markup change, though the added content must not interfere with clicks
- list-style with an empty SVG data URI restores semantics in Safari, but O’Hara saw a gap above each item in IE 11 and Edge 42
The ARIA in HTML specification calls role=”list” on a ul generally unnecessary, then explicitly allows it to reinstate the role when a browser suppresses list semantics after markers are removed. Expect the validator warning anyway (Rocket Validator).
The loss only affects screen reader users, so it never shows up in a visual check. That makes it a web accessibility test item rather than a styling bug.
Add role=”list” to navigation and other lists where the item count helps the listener, and leave decorative lists alone. WebKit’s heuristic exists to cut announcements that add noise.
CSS Lists Examples To Check Out
Task List
See the Pen
Task list by Blaz Kemperle (@blazicke)
on CodePen.
Hover Effect on CSS List Items
See the Pen
CSS List Items Hover Effects by ankit (@celebstori)
on CodePen.
List Rocks the Venetian Blind Animation
See the Pen
Rolldown List by Peter Hrynkow (@peterhry)
on CodePen.
100dayscss
See the Pen
100dayscss #27 by Daiane Assen (@INapta)
on CodePen.
Checklist Magic – Pure CSS Love
See the Pen
Checklist animation – Only CSS by Milan Raring (@milanraring)
on CodePen.
Let’s Get Interactive: The Loose-Leaf Todo
See the Pen
Interactive Loose-Leaf Todo List by Ian (@IanWoodard)
on CodePen.
Color Pop & Dimension by Erin E. Sullivan
See the Pen
Gradient Ordered List by Erin E. Sullivan (@erinesullivan)
on CodePen.
It’s All in the Details: Todo List
See the Pen
Todo list with Little Details (cpc) by Amit Sheen (@amit_sheen)
on CodePen.
Bouncy Add & Delete List Dance
See the Pen
Animated List by Che (@code_dependant)
on CodePen.
Hover & Transition: List Style by Mirza
See the Pen
CSS List Item Hover Effect by Mirza (@Aricati)
on CodePen.
Morphing Moonrocks
See the Pen
Morphing Moonrocks by Chris Coyier (@chriscoyier)
on CodePen.
Forbes Leaderboard Glam
See the Pen
Forbes Leaderboard by Mark Eriksson (@Markshall)
on CodePen.
List Elegance by Armando
See the Pen
CSS list by Armando (@agtzsl27)
on CodePen.
Sticky Note Todo Lists
See the Pen
Todo List with Sticky Notes(CPC) by wheatup (@wheatup)
on CodePen.
Everyday Reminders: To-do List
See the Pen
To-do list by Sabine Robart (@_Sabine)
on CodePen.
When Things Get A Little Sticky
See the Pen
Pretty Sticky by Burmese Potato (@BurmesePotato)
on CodePen.
Planning That Getaway: Vacation Todo List
See the Pen
Vacation Todo List by Taranjot Gill (@tjgill)
on CodePen.
Drop It Like It’s Hot: Numbered Lists
See the Pen
CSS-only numbered lists with “drop” shapes by Ines Montani (@ines)
on CodePen.
Got Them Cards: ListView
See the Pen
Cards – ListView by Sowmya Seshadri (@sowmyaseshadri)
on CodePen.
Animate My Life: CSS-only Todo List Checkbox
See the Pen
☑️ CSS-only Todo List Checkbox Animation by Shaw (@shshaw)
on CodePen.
A Little Extra: Ordered List with that Zero
See the Pen
CSS ordered list with leading zero by Sven Wolfermann (@maddesigns)
on CodePen.
Keep It Classy: Todo List UI with VueJs
See the Pen
Todo List UI with VueJs by Sawsan (@saawsan)
on CodePen.
Bang! Those Custom Bullet Points
See the Pen
Custom Bullet Points by PSR (@psr031)
on CodePen.
Take A Spin: Scroll List Animation
See the Pen
Scroll List Animation by Muhammed Erdem (@JavaScriptJunkie)
on CodePen.
Codepen Squad Roster: Even/Odd Vibes
See the Pen
Codepen Team Member List – Even/Odd Styles by Josetxu (@josetxu)
on CodePen.
Spotlight on the List: Pure HTML and CSS Magic
See the Pen
Featured List pure html css by BdThemes (@bdthemes)
on CodePen.
Shape Shift and Drop: Morphing Menu Action
See the Pen
Morphing tab button with list by Colin Horn (@colinhorn)
on CodePen.
Turn the Fancy Up: The Aesthetically-Pleasing List
See the Pen
Template – Fancy List by Fred (@fcarlin)
on CodePen.
Grow As You Go: Terrarium To-Do
See the Pen
To-Do Terrarium by Olivia Ng (@oliviale)
on CodePen.
For the Bookworms: Compact Quotes
See the Pen
Compact book quotes by lalacode (@lalacode)
on CodePen.
Flip the Script: 3D List Extravaganza by Andrew Canham
See the Pen
3D List expansion by Andrew Canham (@candroo)
on CodePen.
Sleek and Simple: That UI Flow
See the Pen
List UI by Collin Smith (@collincodes)
on CodePen.
Up on the Board: DailyUI’s Leader Rankings
See the Pen
DailyUI #019 – Leaderboard by Fabio Ottaviani (@supah)
on CodePen.
Pen Down Your Needs: Handwritten Shopping Fun
See the Pen
Handwritten shopping list with custom markers by Michelle Barker (@michellebarker)
on CodePen.
CSS Lists FAQ
How do you start an ordered list at a different number or count down?
See the Pen
Reversed and split list by Michelle Barker (@michellebarker)
on CodePen.
Use the HTML attributes. start and reversed set the first number and the direction, and a value attribute on li jumps a single item to any number. The HTML Standard maps all three onto the list-item counter, so author CSS overrides them.
Can a list be split into multiple columns?
See the Pen
Ridiculously responsive lists with CSS Grid by Raúl R Pearson (@raulrpearson)
on CodePen.
Yes. Set column-count on the ul, or switch it to display: grid with a column template. Multi-column layout fills each column top to bottom, while grid fills row by row.
Add break-inside: avoid on li so an item never splits across two columns.
Which selector targets a nested list without touching its parent?
See the Pen
Resetting list counters by Mark (@xmark)
on CodePen.
A descendant selector such as ul ul reaches every nested level, while ul > li > ul hits only the first one. Set list-style-type, margin-block and padding-inline-start on that selector.
Is marker-offset still valid for the gap between marker and text?
No. Older tutorials still list marker-offset, but CSS Lists and Counters Module Level 3 does not define it.
Add padding-inline-start to the li instead. An outside marker sits beyond the principal box, so that padding widens the gap.
Does list-style work on definition lists?
Not by default. The HTML Standard gives dl, dt and dd display: block, so they generate no marker box.
Set display: list-item on dd and the list-style properties apply to it.
Scoping List Resets So Body Copy Keeps Its Bullets
CSS lists stay predictable when the reset targets a class on navigation lists and leaves the bare ul and ol selectors untouched, so body copy keeps its default markers and indentation.
Scope the base list styles to the content container first, add one reset class for navigation lists, and test the navigation in Safari with VoiceOver last. The order matters because each step removes a conflict the next one would otherwise create.
The trade-off is one extra class per navigation list in the markup. That costs less than auditing every ul in the article text.
Framework choice shifts spacing too. Bootstrap’s 2rem padding equals 32px at a 16px root, 8px less than the 40px browser default.
The Safari advice holds as of October 14, 2026 and changes only if WebKit changes or standardizes its layout-list heuristic, which bug 122320 tracks and WebKit treats as intentional. Teams building with utilities can apply the same scoping through Tailwind list utilities.
- What is Backend in Web Development? - September 13, 2026
- What is Frontend Development? - September 11, 2026
- How to Make a Button in Figma: Design Best Practices - September 10, 2026


