Styling List Markers

::marker pseudo-element
Oriol Brufau obrufau@igalia.com

Introduction

Ordered list: <ol>

 <ol>
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
 </ol>
  1. foo
  2. bar
  3. baz

Unordered list: <ul>

 <ul>
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
 </ul>
  • foo
  • bar
  • baz

List item: <li>

HTML: semantically represented by <li> elements.

CSS: any element can be rendered as a list item.

li {
  display: list-item;
}

List item layout

They originate a ::marker pseudo-element.

Automatically increment a list-item counter.

List marker

Symbol or ordinal denoting the start of each list item.

Originated before the ::before.

Customized with list-style or ::marker.

list-style property

Shorthand of list-style-type, list-style-image, list-style-position.

Apply to the list item, not to the ::marker.

Propagated from ancestors via inheritance.

Text-based marker

list-style-type: <counter-style>

  1. decimal
  2. lower-alpha
  3. upper-roman
  4. disc
  5. square
  6. none

Custom text-based marker

list-style-type: <string>

  1. "-"
  2. "+"
  3. "▸"
  4. "★"
  5. "foo bar"

Shipped in Chromium 79 (4.5 years after Firefox).

Image marker

list-style-image: <image>

  1. url(img/smiley.gif)
  2. linear-gradient(blue, red)

Marker position

list-style-position: inside | outside

Inside markers are normal inlines.

Outside markers are special block containers.

  1. inside
  2. inside
  3. multi
    line
  1. outside
  2. outside
  3. multi
    line

::marker pseudo-element

Basic usage

::marker can be used to style list markers.

::marker {
  color: blue;
  font-size: 150%;
  font-style: italic;
}
  1. foo
  2. bar

Tree-abiding

::marker exists in the element tree.

Can be used after ::slotted().

Similar to ::before and ::after.

Property restriction

Only a subset of properties apply to ::marker:

font-*, white-space, color, text-combine-upright, unicode-bidi, direction, animation-*, transition-*, content

Property restriction

Reason: marker layout is not fully defined.

The list of accepted properties is expected to grow.

content property

Specifies the contents of the marker.

Takes precedence over list-style-type and list-style-image.

content: normal is the default.

content: none destroys the marker.

content example

  1. normal
  2. none
  3. "foo"
  4. "foo" url(img/smiley.gif)
  5. counter(list-item)

Default styles

::marker {
  unicode-bidi: isolate;
  font-variant-numeric: tabular-nums;
}

Also for ::before::marker and ::after::marker.

Implementation steps

Selector parser

Let the parser accept selectors like

  • ::marker
  • ::before::marker
  • ::after::marker
  • ::slotted(*)::marker

https://crrev.com/709390

Restrict valid properties

Add a new ValidPropertyFilter for ::marker

Analogous to ::cue and ::first-letter

https://crrev.com/710995

Apply ::marker styles

Markers used to be assigned a manually created ComputedStyle.

Assign the styles from ::marker instead.

https://crrev.com/711883

Support content in LayoutNG

Analogous to ::before and ::after.

Ignore list-style in non-normal cases.

https://crrev.com/718609

Support content: none

Both none and normal used to be stored as nullptr.

New NoneContentData subclass of ContentData.

New ContentBehavesAsNormal and ContentPreventsBoxGeneration methods.

https://crrev.com/732549

Set UA styles

::marker {
  unicode-bidi: isolate;
  font-variant-numeric: tabular-nums;
}

To normal, non-normal, LayoutNG and legacy markers.

https://crrev.com/720875

Fix 99.9% perf regression

https://crrev.com/722421

Devtools styles

https://crrev.com/724094

Devtools tree

https://crrev.com/724122, https://crrev.com/c/1934220

Nested ::marker

Only elements could originate pseudos.

But ::before and ::after can be list items with a nested ::marker.

Nested selectors still don't work.

https://crrev.com/730531

Normal marker as pseudo (LayoutNG)

Markers with content: normal used to be anonymous boxes.

Exposed in devtools and getComputedStyle.

Performance up by 30-40%.

https://crrev.com/731964

Normal marker as pseudo (legacy)

Layout bugs: 1048672, 1049633, 1051114

Selection bug: 1051685

https://crrev.com/745012

Animations & transitions

Allow animation-* and transition-* properties.

But only animate allowed properties.

https://crrev.com/753752

counter(list-item) inside <ol>

Used to be 1 unit too big.

Already affected ::before and ::after.

https://crrev.com/783493

Support content in legacy

Problem: LayoutListMarker can't have children

Solution: new LayoutInsideListMarker and LayoutOutsideListMarker, based on LayoutInline and LayoutBlockFlow.

CL: 2109697, 2109771, 2110630, 2246514, 2252244, 2252041, 2252041, 2246573, 2258732

Ship

CSSMarkerPseudoElement flag enabled by default.

Chromium 86.0.4198.0

https://crrev.com/786976

And others!

~100 patches in total

Several refactorings

Bug fixes

Tests

Layout summary

Layout summary

Inside Outside
Legacy (normal) LayoutListMarker
: LayoutBox
Legacy (custom) LayoutInsideListMarker
: LayoutInline
LayoutOutsideListMarker
: LayoutBlockFlow
LayoutNG LayoutNGInsideListMarker
: LayoutInline
LayoutNGOutsideListMarker
: LayoutNGBlockFlowMixin­<LayoutBlockFlow>

Next

Nested selectors

::before::marker and ::after::marker

Currently valid but ignored.

Complex refactoring of the style resolver.

https://crbug.com/1097992

<details> and <summary>

<details>
  <summary>Summary</summary>
  Details
</details>

Summary Details

The disclosure triangle should be a ::marker

https://crbug.com/590014

Thanks & questions