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 ¶
- Miriam Suzanne
- Robert Flack
- Nicole Sullivan
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:
- [css-grid] Flow multiple elements together into same grid area
- [css-grid] grid area as element
- [css-grid] Decorative grid-cell pseudo-elements
- [css-multicol-2][css-scroll-snap] Snapping to column boxes
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:
- CSS currently supports several overflow values, but the only built-in overflow-access interface is the continuous scrolling container. That can be extended with features such as scroll-animation, scroll-behavior, scroll-snapping, and so on.
- At the other end of a spectrum, most tab and accordion patterns are highly structured & sectioned overflow. Each section has an interactive label that can be used to show or hide the section contents (often one-at-a-time).
- In-between those two extremes, there are a wide range of patterns that get referred to as ‘carousels’. These are not limited to the (problematic) infinite-auto-advancing home-page widgets, but range from video-streaming media selection, to slide shows, and e-commerce product-image viewers.
There have been many attempts over the years to define new tab or carousel components – but they are often held up by:
- The complexity of use-case mixing and matching patterns from scrollers, carousels, tabs, and accordions
- The need to work around many underlying features that are missing from the web platform
- The fact that ‘overflow’ is a presentational concern tightly coupled with device/media/size conditions (and therefor part of CSS), but also requires interactive controls generally left to HTML components or JavaScript.
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:
- On one end, media-scroller carousels (like the Netflix video selection interface) are similar to horizontal overflow with scroll-snapping, and the occasional use of ‘next/previous’ navigation that is sometimes linked to items and sometimes to ‘pages’ of overflow.
- Slide-show style carousels often add small ‘scroll-markers’ (like dots) that represent either the individual items listed, or the viewable pages (if multiple items appear at once).
- Product-image carousels will often replace the dots with thumbnail image scroll-markers, and only show one item per ‘page’. In this case general mouse scrolling (and visible scroll-bars) are often removed in favor of tab controls, while sometimes leaving gesture-based scrolling intact.
- Tabs are similar, but may often removing scroll-controls entirely.
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:
- Managing display of sectioned content within a single document
- 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 example ¶
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:
- next/previous page arrows
- page markers (often dots) to show the number of pages and current active position
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.
- pages are created as-needed based on the amount of content, and the size available to each page.
- by default, a page could be the size of the containing box (not including overflow), so that scrolling the view 100% would bring a new ‘page’ fully into view.
- Pages could be resized, which would impact how much content fits in a given page, how many pages are visible without scrolling, and how many pages are needed for the flow of content.
- Individual pages could be targeted
using a syntax similar to
nth-child
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
- the layout of
::page
elements themselves - 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?
Page navigation / pagination ¶
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:
- Keeping the markers inline with the items they mark, vs creating a distinct table-of-contents before/after the flow of items/pages
- Managing the link between markers and the content they mark
- Maintaining the current state of active content and markers
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:
- Would we need a way to request markers generated per-item, per-page, or per-snap-target? Can those options be combined?
- Can the
scroll-display
can be set differently for inline/block axis? - Can values be combined to show both a continuous-scroll bar and also scroll markers?
- Do previous/next arrows fall into a similar category? While they are simpler for authors to provide in-markup, they require a fair amount of state-management to get right.
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:
- Defined by Javascript handlers.
The amount to scroll can be calculated,
rely on css-scroll-snap-1 6.2 to choose the next snap point from a directional scroll (e.g.
scrollBy({left: 1})
), or use the proposedscrollTo({left: 'snap-next'})
explainer. - Defined via page anchors (e.g.
<a href="#page2">
). This requires the author to create next / previous links for every page, which are only visible on that page, and to explicitly paginate their content independent of screen size. - Implicit, by using new pseudo-elements or attributes (e.g.
<button type="next">
).
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:
- Do we need a new element or attribute for assigning scroll-marker behavior? Or do authors have to do all the interaction/state-management manually?
- Is there a way for authors to keep the marker markup inline, and still ‘hoist’ the element into a parent grid context?
<tabs>
<section>
<h2 marker>tab label</h2>
<div class='panel'>tab panel</div>
</section>
…
</tabs>
Layout of scroll-markers / tabs ¶
- [css-grid] Flow multiple elements together into same grid area
- [css-grid] grid area as element
- [css-grid] Decorative grid-cell pseudo-elements
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 ¶
- spicy-sections from OpenUI
::scroll-marker
proposal from Una Kravets & Adam Argyle
Stakeholder Feedback / Opposition ¶
[TBD]
References & Acknowledgements ¶
Much of this work is based on the research and proposals compiled by others:
- Rachel Andrew
- Una Kravets
- Adam Argyle
- Brian Kardell, Dave Rupert, Jon Neal, Sarah Higley, Scott O’Hara, and others in the OpenUI Community Group.
Note:
- document snapping approach
- scroll markers are tabs (aria)
- focus groups
- gestures vs mouse (mouse fling?)