CSS Styling Lists

Every <ul> and <ol> element renders with a browser-default marker — a bullet or a number — and a chunk of default indentation. CSS gives you full control over that marker: what shape or symbol it uses, where it sits relative to the text, and even its own color and font, independent of the list text itself. Mastering list styling matters because lists show up everywhere: navigation menus, article content, step-by-step instructions, and card layouts all lean on <ul>/<ol> under the hood, so knowing how to reset and restyle them is a core CSS skill.

Overview: How the Browser Renders Lists

In the CSS box model, each <li> is a block-level box with display: list-item. This special display value tells the rendering engine to generate two things for that element: the normal content box (holding the list item’s text and children), and a separate marker box that sits either outside or inside that content box. The marker box is not part of the document’s accessible text by default in the same way regular content is, but it is rendered and, in modern browsers, fully styleable through the ::marker pseudo-element.

Two properties control the marker’s appearance and placement: list-style-type decides *what* the marker looks like (a disc, a decimal number, a custom string, or nothing), and list-style-position decides *where* the marker box is placed relative to the content box — outside (the default, marker sits in the margin area, content wraps in a clean vertical column) or inside (the marker becomes part of the content’s inline flow, so wrapped lines align under the marker rather than under the first letter). A third property, list-style-image, lets you swap the marker for a custom graphic entirely.

Because <ul> and <ol> also ship with user-agent stylesheet defaults for margin and padding (typically around 40px of left padding, used to make room for the outside marker), truly custom list designs usually involve resetting those box-model properties alongside the list-style properties.

Syntax

list-style-type: disc | circle | square | decimal | upper-roman | lower-alpha | none | <string>;
list-style-position: outside | inside;
list-style-image: none | url("marker.svg");
list-style: <type> <position> <image>; /* shorthand, any order, any part omittable */

::marker {
  /* a small, limited set of properties works here */
  color: value;
  content: value;
  font-*: value;
}
  • list-style-type — the marker’s shape, numbering scheme, or a custom quoted string used as the marker glyph.
  • list-style-position — whether the marker sits outside the content box (default) or inside it, sharing the same line box as the text.
  • list-style-image — replaces the marker with a bitmap or vector image; falls back to list-style-type if the image fails to load.
  • list-style — shorthand combining all three in any order; omitted parts keep their initial values.
  • ::marker — a pseudo-element that targets just the marker box, letting you set its color, content, and a restricted set of font/text properties independently of the list item’s text.

Common list-style-type values

Value Renders as
disc Filled circle (default for <ul>)
circle Hollow circle
square Filled square
decimal 1, 2, 3… (default for <ol>)
decimal-leading-zero 01, 02, 03…
upper-roman / lower-roman I, II, III… / i, ii, iii…
upper-alpha / lower-alpha A, B, C… / a, b, c…
none No marker box generated at all

Examples

Example 1: Basic marker styling

ul.fruits {
  list-style-type: square;
  list-style-position: inside;
  color: #333;
}

ol.steps {
  list-style-type: upper-roman;
  padding-left: 2rem;
}

Result: The <ul class=”fruits”> items each get a filled square marker that sits inside the content box, so a wrapped second line of text lines up under the square rather than under the first letter. The <ol class=”steps”> items are numbered with uppercase Roman numerals (I., II., III.…) instead of the default Arabic numerals, indented 2rem from the left edge.

This example shows the two most-reached-for properties working together: list-style-type swaps the glyph, and list-style-position changes how wrapped text aligns to it.

Example 2: Custom marker content and color with ::marker

ul.tasks {
  list-style-type: disc;
  padding-left: 1.5rem;
}

ul.tasks li::marker {
  color: #e63946;
  font-weight: bold;
  content: "\2713  ";
}

Result: Instead of the default black disc, every <li> in <ul class=”tasks”> shows a bold red checkmark (✓) as its marker, followed by a space, while the list item’s own text stays whatever color it was already set to.

This demonstrates the key advantage of ::marker: it lets you restyle the bullet or number independently of the text content, something that was impossible with plain list-style-type alone. Note that ::marker only accepts a limited property set — mainly color, content, font-*, white-space, and a couple of animation-related properties — layout properties like padding or width are not supported on it.

Example 3: A bullet-free horizontal navigation list

nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 1.5rem;
}

nav ul li a {
  text-decoration: none;
  color: #1d3557;
  font-weight: 600;
}

nav ul li a:hover {
  text-decoration: underline;
}

Result: The default bullets and the browser’s built-in left padding/margin disappear entirely, and the <li> elements lay out in a single horizontal row with 1.5rem of space between each one (thanks to Flexbox’s gap), producing a typical top-navigation menu. Each link is unstyled by default (no underline) and gains an underline only on hover.

This is the single most common real-world list pattern on the web: navigation bars are almost always built from a semantic <ul> that CSS strips of its list appearance and reflows with Flexbox or Grid.

How It Works Step by Step

When the browser lays out a <ul> or <ol>, it processes each child <li> roughly like this:

  • 1. It sees display: list-item (the default UA-stylesheet value for <li>) and generates a content box for the item plus a separate marker box.
  • 2. It resolves list-style-image first — if a valid image is set, that becomes the marker’s rendered content.
  • 3. If no image (or it failed to load), it falls back to list-style-type to compute the marker’s glyph — a bullet shape, or the next value in the numbering sequence for ordered lists (the counter increments automatically per <li>, or per any explicit CSS counter you apply).
  • 4. It checks list-style-position to decide where to place that marker box: outside pulls it into the margin area to the left of the content box (this is why removing a parent’s padding can make bullets appear clipped or hidden); inside places it as the first inline box inside the content box, participating in normal text flow and line-wrapping.
  • 5. It resolves the marker’s own styling: unless overridden by ::marker, the marker inherits color and most font properties from the <li>. If a ::marker rule exists, its declared properties win for the marker only, leaving the <li>’s text styling untouched.

Common Mistakes

Mistake 1: Assuming color on the <li> only affects the marker

Wrong assumption — developers often write this expecting only the bullet to turn red:

ul.menu li {
  color: red;
}

Because the marker inherits color from the list item by default, this makes the link text red too, which is usually not the intent. To restyle just the marker, target ::marker directly and leave the item’s own color alone:

ul.menu li {
  color: inherit;
}

ul.menu li::marker {
  color: red;
}

Mistake 2: Removing markers but forgetting the default box-model spacing

Setting only list-style: none removes the marker glyph but leaves the browser’s default margin/padding on the list in place, so the content is left with a mysterious block of empty indentation and no bullet to explain it:

ul.no-bullets {
  list-style: none;
}

The fix is to zero out the box-model spacing at the same time you remove the marker:

ul.no-bullets {
  list-style: none;
  margin: 0;
  padding: 0;
}

Mistake 3: A syntax typo in a ::marker rule

A missing semicolon breaks parsing of the whole declaration block, silently dropping every rule after the error point in that block:

li::marker {
  content: "> "
  color: red;
}

Always terminate every declaration with a semicolon, especially after quoted content values, which are easy to forget:

li::marker {
  content: "> ";
  color: red;
}

Best Practices

  • When stripping default list styling for layout purposes (nav bars, card grids), always reset margin and padding alongside list-style: none.
  • Prefer ::marker over old hacks like list-style-image with a background sprite or wrapping marker text in a <span> — it’s simpler and keeps semantics intact.
  • Remember ::marker only supports a small property set (color, content, font properties); don’t reach for padding, width, or background on it — use a wrapping element instead if you need those.
  • Use list-style-position: inside when you want wrapped lines to align under the marker (common in tight card/UI text), and outside (the default) for standard document-style reading content.
  • When a <ul> is being used purely for layout (like a nav menu) rather than as a visible list, removing markers is appropriate; don’t remove them from genuinely list-like content just for aesthetics without another visual cue separating items.
  • Test custom numbering types (upper-roman, decimal-leading-zero, etc.) with real content lengths — longer markers may need extra padding-left to avoid crowding the text.

Practice Exercises

  • 1. Style an <ol> of recipe steps so it uses decimal-leading-zero numbering, with the markers colored a distinct accent color via ::marker while the step text stays black.
  • 2. Build a horizontal footer link list from a <ul>: remove bullets and default spacing, lay the items out with Flexbox and a gap, and add a hover underline to the links (hint: you’ll need to reset list-style, margin, and padding together).
  • 3. Take a <ul> of long, wrapping text items and compare how the second line of text aligns under list-style-position: outside versus inside; write down which one you’d use for a dense settings-menu UI and why.

Summary

  • list-style-type controls the marker’s shape or numbering scheme; list-style-position controls whether it sits outside or inside the content box.
  • list-style-image swaps the marker for a custom graphic, falling back to list-style-type if it fails to load.
  • list-style is a shorthand for all three, values in any order, any part omittable.
  • ::marker is the modern way to style just the marker (color, content, limited font properties) without affecting the list item’s own text.
  • <ul>/<ol> ship with default margin and padding for the outside marker — reset both when building custom layouts like navigation bars.
  • Markers inherit color and font properties from their <li> unless a ::marker rule overrides them.