CSS Overflow - Broad Research

Table of Contents
Archived:

🚚 This content is being maintained elsewhere. Our notes are likely out-of-date.

See Rob’s Carousel Explainer, Related CSSWG discussion, and upcoming CSS-Overflow-5 specification. for details.

Note:

This is a high level review of issues in overflow, and potential features to explore. We’ll break out individual explainers for specific feature proposals.

Authors

Participate

Anyone can comment on, file issues against, or contribute to this explainer on GitHub:

We also rely heavily on some existing proposals discussed in the CSS Working Group issue tracker:

Introduction

Managing layout and design on the web requires careful control of ‘overflow’ – allowing content to be hidden when it’s not needed, but accessibly discovered and revealed when necessary.

There are many overlapping user-interface design patterns that can be used – from standard continuous scrolling, to scroll-snapping, slideshows, carousels, accordions, tabs, and various hybrid approaches. The lines between these patterns are often somewhat blurry:

There have been many attempts over the years to define new tab or carousel components – but they are often held up by:

Goals

The goal of this explainer is not to propose yet another component, or provide a one-size-fits-all solution for all overflowing UI – but to consider some baseline improvements to CSS overflow that could help authors flesh out the details of each pattern more elegantly, consistently, and accessibly. This could also help form the scaffolding for future components, as needed.

In some cases, we’re able to propose a path forward – and in other cases, we simply document the problem and some of the tradeoffs with different approaches.

Overlapping patterns

The patterns we’re looking at fall along a continuum of scrolled and paged overflow:

Rather than thinking of these as distinct patterns, we want to understand the underlying set of controls that are combined to create any given variation. Primarily: paged overflow and interactive scroll markers in a variety of forms.

Overflow changes based on context

While HTML, CSS, and JS often have distinct and separate ‘concerns’ – semantics, presentation, and interaction – overflow falls into a middle ground that can be hard to break apart. Content that overflows on one device, or at a particular screen size, may not need to overflow in other situations. That’s a presentational concern, rather than a semantic one, and often needs to be handled using media queries.

However, semantics do play a clear role in what patterns make sense (the difference between a carousel and tabs often comes down to how content is sectioned), and different types of ‘overflow’ require different interactions. Any solutions provided here have to account for changes in overflow based on device/media/container context – and help authors access the proper interactions and accessible roles for each type of overflow.

Non-goals

Application-Level Tabs

The ‘tabs’ interface pattern has two common and somewhat distinct use-cases:

  1. Managing display of sectioned content within a single document
  2. Managing display of multiple documents within an application

The former can be seen as a form of ‘content overflow’ covered by by this proposal. When searching within a document, users would expect content in hidden sections to be ‘discoverable’. On the other hand, application level tabs (such as in a browser or text-editor) represent distinct documents rather than ‘overflow’. Users would not expect a search in one document to reveal content in another tab.

This proposal is specific to handling content overflow presentation in HTML and CSS, rather than providing a generic solution to the ‘tabs’ interface design pattern.

Auto-advancing & Cyclic Home Page Carousels

The term ‘carousel’ has a strong negative connotation in web design circles, thanks to the poor user-experience of home page carousels used to display an endless cycle of ‘featured’ content.

Readers rarely interact with this pattern, and generally don’t see content beyond the first page of the carousel, unless forced by auto-advancement, which causes a whole new list of accessibility and usability issues.

While that makes us hesitant to use the term ‘carousel’ here, there are a range of much more essential use-cases that fall under the term – and it’s helpful to understand the somewhat fluid and overlapping nature of scroll/carousel/tab/accordion patterns.

Virtual lists

Virtual lists are tightly related to overflow, but outside the scope of this document.

Possible solutions

Rather than proposing a single complete solution to these fluid ux patterns, we think it would be useful to address the core HTML/CSS layout and overflow issues that make such an element difficult to implement. This should provide a stepping-stone for both authors and browser vendors to explore and develop more narrowly defined web-components or HTML elements where they make sense.

There has already been some significant progress along these lines. The scroll-snap properties, scroll-linked animations, and size container queries all help authors better address overflow. Below, we’ll explore some of the features that are still missing or difficult for authors to get right.

Carousel boxes

As a strawman example, a developer should be able to use the following HTML to create the visualized carousel:

<carousel>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</carousel>

The visualized carousel can be constructed with the following style dynamically generating all of the required components:

carousel {
display: grid;
grid-template:
'previous scroller next' 1fr
'. markers .' auto
/ auto 1fr auto;

> li {
/* Flow into scroller grid-flow defined below */
grid-flow: --scroller;
}

&::grid-flow(--scroller) {
grid-area: scroller;
/* Paginate overflow? */
overflow: paginate;
scroll-snap-type: x mandatory;

&::page {
/* style all pages */
scroll-snap-align: center;

&:not(:active) {
interactivity: inert;
opacity: .5;
}

/* Create markers for each page */
::marker {
/* Flow them into the markers grid-flow */
grid-flow: --markers;
}
}
}

&::grid-flow(--markers) {
grid-area: markers;
}

&::next {
grid-area: next;
}

&::previous {
grid-area: previous;
}
}

Rob has put together a prototype of this feature, and will write a more detailed explainer for the features required.

Paged overflow, in the browser

Simple carousels (or ‘media-scrollers’) are often built with scrollable overflow and scroll-snap targets. What makes it a ‘carousel’ is the addition of ‘paged’ navigation options:

Paged overflow isn’t a new idea in CSS. While implementations have not always been complete or consistent, paged overflow is already well-defined for print, and has a number of other use-cases if it were available in the browser.

Establishing paged overflow on an element would generate ::page pseudo-element fragmentainers for descendant elements & content to flow through.

It’s not immediately clear what syntax would be best for invoking this sort of paged overflow. While this behavior is related to both overflow and (in some ways) display, neither property seems like a particularly good fit.

Paged use-cases (e.g. carousels) might involve scrolling between pages, while others (e.g. multicol wrapping) may not. So pagination is not necessarily alternative to scrolled overflow. Even if paged overflow had an auto-like scroll behavior, to allow scrolling and non-scrolling pages, single-axis paged overflow(-x/-y) doesn’t make much sense.

Authors will also need a mechanism for handling the layout of elements within pages (and the layout of the pages themselves) – both of which require display values. It’s possible the pagination and layout controls could be combined in a single property (e.g. display: paged grid) if it makes sense for them to cascade together. Since display is a shorthand for inside and outside values, pagination would either need to be added to one of those properties or a third new display sub-property.

Styling paged overflow

As mentioned above, authors will need a way to provide display values for both

  1. the layout of ::page elements themselves
  2. the layout of child contents flowing through those pages

I would expect the overflowing/paginated parent element to handle the layout of pages, in which case a new (::page-contents?) could be used for the layout of content flowing through pages (or vice versa). An additional wrapping pseudo-element like this might only support a limited subset of CSS properties or pseudo-classes.

In some cases, an author would style all ::page elements, but it would often be useful to target specific pages with e.g. ::page:nth-child(even) (or ::page(even)). However, there might be recursion issues with other combinations, such as ::page:nth-last-child(even) or ::page:focus-within, if the styles applied could change the number of pages or placement of content in those pages.

Authors will often want to style the active page and marker. Authors could use :snapped from css-scroll-snap-2 in combination with snapped pages. E.g.

::page {
scroll-snap-align: start;
}
::page:snapped-inline::marker {
/* Highlight active marker. */
outline: 2px solid blue;
}
Note:

Could these pseudo-classes be used for paged (e.g. print) media as well?

One of the design patterns often associated with a ‘carousel’ component is the use of ‘dots’ for page-based navigation. Rather than (or in addition to) a continuous scrollbar, readers can select the page they want to move to. These ‘scroll markers’ or ‘pagination markers’ are much like list markers – often taking the form of dots/bullets, but occasionally styled as counters, or even thumbnails and other content.

Sometimes those page-access markers represent individual items in a list. This per-item marking has a large overlap with ‘tabs’ – a line that blurs in ‘product-image’ carousels for example. Where’s the line between a carousel-marker with an image thumbnail, and a section tab with a text label?

In general, item-based markers can be provided by authors in the markup, along with the items they mark. In that case, the main concerns are:

We’ll explore some of those issues more below.

However, many scroll markers represent the abstract ‘pages’ generated by a flow of content – where the number of markers may change based on the size of a container – or arbitrary scroll-snap points that combine items and pages. In those situations, a marker would need to be generated either using JavaScript, or (ideally) by the browser.

Una Kravets and Adam Argyle have explored this in an early draft ::scroll-marker explainer. In their proposal, markers can be generated by setting a scroll-display property to one of bar (the default), auto, or marker on the scrolling parent. This leaves a number of interesting questions open for consideration:

Interactive, state-managing, generated content?

One of the most difficult aspects in building web carousels, slideshows, and tabs is proper ‘wiring’ for the interactive navigation (dots, tabs, prev/next) – with proper accessibility and keyboard interaction, along with scroll-position and active-state management. This is especially true when the ‘targets’ of that navigation are based on fluid overflow rather than specific DOM elements.

This is the primary reason that authors would prefer a built-in web platform solution, but it also raises big questions about using ‘generated content’ for interactive controls.

The generated markers would also need to track and expose the current active state - providing a way to style the active marker.

Next / previous buttons

Many ‘carousel’ components offer next and previous buttons. These could be declared explicitly in HTML, or constructed as interactive pseudo-elements (e.g. ::next/::previous). The behavior of these could be defined in many ways:

Per-item markers & tabs

In more tab-like cases, where a predictable number of markers are needed – e.g. one marker per list-item – they could be provided in markup.

This has several advantages, since it gives authors more control, and doesn’t rely on interactive pseudo-elements. However, it still comes with it’s own set of issues:

<tabs>
<section>
<h2 marker>tab label</h2>
<div class='panel'>tab panel</div>
</section>

</tabs>

Layout of scroll-markers / tabs

Accessibility

Exclude offscreen content

It is common practice for carousels to only include content on the active screen in the accessibility tree and in tab order. Other screens are accessed via buttons or links. In the WAI tutorial, this is accomplished by setting the aria-hidden attribute from Javascript. A pure declarative solution could be to allow setting inertness via CSS. E.g.

::page:not(:active) {
interactivity: inert;
}

See [css-ui] Should inertness be exposed as CSS property?.

Interactive pseudo-elements

If we have interactive pseudo elements they will need appropriate roles. These could be fixed for the pseudo element type (e.g. next / previous are buttons).

Key scenarios

[TBD]

Detailed design discussion & alternatives

[TBD]

Prior Art & Context

Stakeholder Feedback / Opposition

[TBD]

References & Acknowledgements

Much of this work is based on the research and proposals compiled by others:

Note:
  • document snapping approach
  • scroll markers are tabs (aria)
  • focus groups
  • gestures vs mouse (mouse fling?)