v154 · scroll-marker-group · ::column composition

Column pager

The spec's most interesting sentence: when a ::scroll-marker originates from a ::column fragment, tabs mode gives the tabpanel role to the column's originating element. In other words: a plain flowing article, fragmented into pages by CSS multicol, becomes a paged, tabbed reader whose page dots are derived — grow the type and the browser re-paginates and re-derives the dots, roles and focus order, with no element per page in the markup.

A reader that derives its own pager

Fragmentation is pagination

Multicol has always chopped a flow of text into column boxes; what it lacked was any way to travel between them when they overflow horizontally. Give the container a fixed height and column-fill: auto, and the columns march off to the right — a book, waiting for a pager.

The carousel primitives supply the pager. Each generated ::column fragment can carry a ::scroll-marker, and the markers collect into the container's ::scroll-marker-group. The dots below this text are exactly that: one per column box, none in the markup.

Modes finish the job. With scroll-marker-group: after tabs, the group is a tablist and each dot a tab — and because the panels here are column fragments rather than elements, the spec routes the tabpanel role to the fragments' originating element. Assistive technology sees a coherent paged widget over what is, in the DOM, one run of prose.

Now resize the type with the slider above. The browser re-fragments the text into a different number of columns, and the marker group re-derives itself: more pages, more dots, same zero lines of pagination script. That is the promise of deriving UI from layout instead of mirroring layout in script.

The rule

.reader {
  columns: 1;             /* fragment the article        */
  column-fill: auto;
  height: 15rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-marker-group: after tabs;   /* pager = tablist  */
}
.reader::column { scroll-snap-align: center; }
.reader::column::scroll-marker { content: ""; /* one dot per page */ }
.reader::column::scroll-marker:target-current { background: black; }

JavaScript cannot enumerate ::column fragments, so the page count above is measured honestly from geometry (scrollWidth / clientWidth) — it is a readout of what the fragmentation produced, not a driver of it.

see also