Skip to content

Commit 75a4d2b

Browse files
authored
[css-overflow-5] Add an explainer for carousel CSS features. (#11342)
1 parent 2d97a0a commit 75a4d2b

File tree

1 file changed

+365
-0
lines changed

1 file changed

+365
-0
lines changed

css-overflow-5/carousel-explainer.md

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
# Carousel CSS features
2+
3+
## Problem description
4+
5+
Carousels are an often used design pattern on the web.
6+
They are used in a variety of contexts,
7+
from product listing pages to slideshow like content.
8+
OpenUI has [explored a range of carousel designs](https://open-ui.org/components/carousel.research/),
9+
showing that the specific layout and appearance can vary dramatically.
10+
They are also provided by many frameworks as components,
11+
however implementing a carousel correctly is complicated
12+
and often results in inconsistent and sometimes inaccessible implementations.
13+
14+
There are a [variety of problems being solved by carousels](https://css.oddbird.net/overflow/explainer/),
15+
which we believe could be provided by a set of CSS features.
16+
Developers could then combine these CSS features to create the various designs.
17+
CSS-only component libraries could be built to further simplify this process.
18+
19+
### Scroll markers
20+
21+
Many carousels have markers or thumbnails
22+
which provide convenient navigation
23+
and a sense of overall progress through the carousel.
24+
25+
For individual items, an author *can* do this manually,
26+
though it requires writing extra elements
27+
which need to be kept up to date with the items to which they scroll.
28+
Script also needs to be used to get the desired scrolling behavior.
29+
30+
For dynamically content-sized pages, this can only currently be done with script which generates DOM.
31+
By having a way to automatically generate markers,
32+
many more advanced UI patterns can be solved in CSS.
33+
34+
#### Requirements
35+
36+
Scroll markers require the combination of several behaviors:
37+
38+
1. They should scroll to the target on activation,
39+
2. only one of the scroll markers in a group should be active (and focusable) at a time (see [roving tab-index](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets#technique_1_roving_tabindex)),
40+
3. arrow keys should cycle between the other markers,
41+
4. the correct one is automatically activated as a result of scrolling, and
42+
5. The active marker should be stylable.
43+
44+
### Scroll buttons
45+
46+
Many carousels provide buttons for direct navigation to the next / previous item or page of content.
47+
While they could create these `<button>` elements as part of the page,
48+
they are not semantically part of the list of items.
49+
Further, the code for these adds a bit of additional complexity in terms of focus order, and tracking enabled state based on scroll position.
50+
51+
### CSS inert
52+
53+
It is common and [recommended practice](https://www.w3.org/WAI/tutorials/carousels/working-example/) (See use of aria-hidden) that
54+
only content on the active page is included in focus order.
55+
Users must use the carousel navigation controls (e.g. buttons / markers / swipe)
56+
to access content on future pages.
57+
58+
This is typically accomplished through the use of Javascript,
59+
as can be seen in the [Web Accessibility Initiative Carousel example](https://www.w3.org/WAI/tutorials/carousels/working-example/).
60+
However, this should be simple to express and declare in CSS.
61+
62+
### Stylable columns
63+
64+
Carousels often show lists of many items as a single group per page.
65+
Typically the user advances through one group at time.
66+
Many existing carousel libraries have complex logic
67+
to determine the number of items that fit per page and construct a per-page DOM.
68+
69+
However, automatic UA pagination can be done today through the use of column fragmentation, ([e.g.](https://jsbin.com/modewaj/edit?html,output)):
70+
```css
71+
.paginate {
72+
overflow-x: scroll;
73+
columns: 1;
74+
}
75+
```
76+
77+
This automatically handles paginating the content, but developers need access to a few additional styles to make these participate in a carousel UX.
78+
Specifically, developers need to be able to:
79+
* Set scroll snap points on them.
80+
* Implicitly create scroll markers for each column.
81+
82+
## Proposed solutions
83+
84+
Many of these aspects of carousel experiences are highly differentiated and customizable
85+
or are building blocks that are shared by different experiences.
86+
As such, the proposed solutions here identify the specific features
87+
that make it possible to pick and style the pieces a developer may want for their carousel-like experience.
88+
89+
### Scroll markers
90+
91+
Anchor links are already used today to provide some of the behaviors of scroll markers.
92+
We can extend anchor links to support the missing behaviors
93+
by adding a grouping attribute. E.g.
94+
95+
```html
96+
<style>
97+
.container {
98+
scroll-marker-contain: auto;
99+
}
100+
</style>
101+
<ul class="container">
102+
<li><a href="#intro">Intro</a></li>
103+
<li><a href="#overview">Overview</a></li>
104+
<li><a href="#summary">Summary</a></li>
105+
<li><a href="#faq">FAQ</a></li>
106+
</ul>
107+
```
108+
109+
By declaring that all of the anchor links in the list are part of a contained group,
110+
the UA can ensure that one of them is determined to be the current marker,
111+
allowing it to be styled with `::target-current`.
112+
113+
```css
114+
a::target-current {
115+
font-weight: bold;
116+
}
117+
```
118+
119+
Roving tab-index selection can be added by adding the focusgroup attribute,
120+
combined with the [guaranteed tab stop](https://open-ui.org/components/focusgroup.explainer/#guaranteed-tab-stop)
121+
with the [last-focused element](https://open-ui.org/components/focusgroup.explainer/#last-focused-memory) automatically updated to the active marker.
122+
123+
```html
124+
<ul class="container" focusgroup>
125+
<li><a tabindex="-1" href="#intro">Intro</a></li>
126+
<li><a tabindex="-1" href="#overview">Overview</a></li>
127+
<li><a tabindex="-1" href="#summary">Summary</a></li>
128+
<li><a tabindex="-1" href="#faq">FAQ</a></li>
129+
</ul>
130+
```
131+
132+
#### ::scroll-marker-group and ::scroll-marker pseudo-elements
133+
134+
Navigation controls are not part of the semantic content of the page.
135+
Sometimes, they may be a presentational choice for a semantic list of content.
136+
By allowing the creation of scroll marker groups from semantic lists,
137+
developers can automatically augment existing content without HTML modification.
138+
139+
E.g.
140+
```html
141+
<style>
142+
@media screen {
143+
.slides {
144+
overflow: auto;
145+
counter-reset: slide-number;
146+
scroll-marker-group: after;
147+
}
148+
.slide::scroll-marker {
149+
counter-increment: slide-number;
150+
content: counter(slide-number);
151+
}
152+
}
153+
154+
@media print {
155+
.slides {
156+
counter-reset: slide-number;
157+
}
158+
.slide::before {
159+
counter-increment: slide-number;
160+
content: "Slide " counter(slide-number);
161+
}
162+
}
163+
</style>
164+
<div class=slides>
165+
<div class=slide></div>
166+
<div class=slide></div>
167+
<div class=slide></div>
168+
<div class=slide></div>
169+
<div class=slide></div>
170+
<div class=slideshow>
171+
```
172+
173+
This allows for the HTML structure to be focused on the content,
174+
with the controls to be generated automatically,
175+
optionally dependent on presentation media or screen real estate.
176+
177+
### Scroll buttons
178+
179+
Similar to scroll markers, it should be easy to create scroll buttons.
180+
If a developer has explicit `<button>` elements, they could use the [commandfor](https://open-ui.org/components/invokers.explainer/) attribute targeting the scrolling container
181+
with an [action hint](https://open-ui.org/components/invokers.explainer/) of `scroll-<direction>`.
182+
183+
For use cases where the developer wishes to dynamically add scroll button controls,
184+
the `::scroll-button(<direction>)` pseudo-element will establish a scroll button which
185+
on activation scrolls the scrolling container of its owning element. E.g.
186+
187+
```css
188+
.scroller {
189+
overflow: auto;
190+
}
191+
192+
.scroller::scroll-button(down) {
193+
content: "v";
194+
}
195+
196+
.scroller::scroll-button(up) {
197+
content: "^";
198+
}
199+
```
200+
201+
These pseudo-elements are focusable and behave as a button.
202+
When activated, a scroll is performed in the indicated direction by a page (e.g. 85% of the scrollport similar to pressing the Page Down key on the keyboard).
203+
When it is not possible to scroll in that direction, they are disabled and match the `:disabled` pseudo-class.
204+
205+
### CSS inert
206+
207+
The interactivity property [#10711](https://github.com/w3c/csswg-drafts/issues/10711)
208+
combined with either a view timeline or snapped scroll state container query
209+
can be used to make the offscreen content inert. E.g.
210+
211+
Using a view timeline:
212+
```css
213+
@keyframes interactive {
214+
0% { interactivity: auto; }
215+
100% { interactivity: auto; }
216+
}
217+
218+
.slide {
219+
/* Inert out of view items */
220+
interactivity: inert;
221+
/* Make interactive when in view */
222+
animation: interactive;
223+
animation-timeline: view(inline);
224+
}
225+
```
226+
227+
Using a scroll-state query:
228+
```css
229+
.slide {
230+
@container not scroll-state(snapped) {
231+
interactivity: inert;
232+
}
233+
}
234+
```
235+
236+
### Stylable columns
237+
238+
Column fragmentation allows for automatic pagination ([example](https://jsbin.com/modewaj/edit?html,output)).
239+
Developers need access to a few additional styles to make the columns participate in a carousel UX.
240+
241+
The `::column` pseudo-element allows applying a limited set of styles to generated columns.
242+
Specifically, this is limited to styles which do not affect the layout,
243+
and thus can be applied post-layout.
244+
245+
E.g. the following [example](https://jsbin.com/defazup/edit?html,output) automatically paginates a list of items snapping each page into view.
246+
```css
247+
ul {
248+
overflow: auto;
249+
container-type: size;
250+
columns: 1;
251+
}
252+
ul::column {
253+
scroll-snap-align: center;
254+
}
255+
```
256+
257+
This pseudo-element can additionally be used for the creation of `::scroll-marker` pseudo-elements:
258+
```css
259+
ul::column::scroll-marker {
260+
/* Marker styling */
261+
}
262+
```
263+
264+
## Putting it all together
265+
266+
With the features described, developers can create an automatically paginated carousel UX from a semantic list of items, e.g.
267+
268+
```html
269+
<ul class=carousel>
270+
<li>Item 1</li>
271+
<li>Item 2</li>
272+
...
273+
<li>Item n - 1</li>
274+
<li>Item n</li>
275+
</ul>
276+
```
277+
278+
With the following CSS,
279+
the above HTML will be automatically paginated with
280+
a scroll marker per page with the active page highlighted,
281+
scroll buttons to go to the next / previous page,
282+
snapping to pages,
283+
and only having the user tab through content on the current page.
284+
285+
```css
286+
.carousel {
287+
/* Automatically paginate into scrollport-sized columns. */
288+
columns: 1;
289+
290+
/* Enable scrolling and set scrolling behaviors. */
291+
overflow-x: auto;
292+
overscroll-behavior-x: contain;
293+
scroll-snap-type: x mandatory;
294+
scroll-behavior: smooth;
295+
296+
/* Scroll marker creation and styling. */
297+
scroll-marker-group: after;
298+
&::scroll-marker-group {
299+
height: 40px;
300+
overflow-x: auto;
301+
}
302+
&::column::scroll-marker {
303+
content: ' ';
304+
&:target-current {
305+
background: SelectedItem content-box;
306+
}
307+
&:focus {
308+
border-color: Highlight;
309+
}
310+
}
311+
312+
/* Scroll button creation and styling. */
313+
&::scroll-button(*) {
314+
position: absolute;
315+
}
316+
&::scroll-button(left) {
317+
content: '<';
318+
left: 0;
319+
}
320+
&::scroll-button(right) {
321+
content: '>';
322+
right: 0;
323+
}
324+
325+
/* Snap to columns as well. */
326+
&::column {
327+
scroll-snap-align: center;
328+
}
329+
330+
& li {
331+
/* Inert out of view items */
332+
interactivity: inert;
333+
/* Make interactive when in view */
334+
animation: interactive;
335+
animation-timeline: view(inline);
336+
}
337+
}
338+
339+
@keyframes interactive {
340+
0% { interactivity: auto; }
341+
100% { interactivity: auto; }
342+
}
343+
```
344+
345+
## Future work
346+
347+
There are a few carousel designs not currently addressed.
348+
This section enumerates and explores these areas.
349+
350+
### Cyclic carousels
351+
352+
Many carousels allow scrolling from the last item in the list to the first, or vice versa.
353+
We expect that some form of cyclic overflow support (e.g. [#5411](https://github.com/w3c/csswg-drafts/issues/5411))
354+
will make this trivial for authors to enable on top of these other features.
355+
356+
In the interim, authors could continue to use script, as they do today,
357+
to move content to the start / end of the carousel so that it can continuously scroll,
358+
or by overriding the next button behavior when at the last item to scroll back to the beginning.
359+
360+
### Auto-advancing carousels
361+
362+
Auto-advancing carousels introduces many potential accessibility issues if not implemented properly.
363+
The [Web Accessibility Initiative Carousel Animations](https://www.w3.org/WAI/tutorials/carousels/animations/) guidelines explores the necessary affordances.
364+
Most carousel experiences can be authored without automatically advancing sections,
365+
and in the mean-time author script could implement the animation following the WAI guidelines.

0 commit comments

Comments
 (0)