Carousel
A carousel shows one thing several ways in the space of one: tall cards side by side, one in view and the next cut at the edge, or one slide at a time across the whole width. The slides snap sideways, one to a stop, under a finger, a trackpad or the keyboard, and a row of page dots one square below says which slide you are on and takes you to any other. Previous and next buttons can stand beside the dots.
With slide-width="auto" it is a strip: cards of their own widths, tall ones and wide ones, a few in view and the next cut at the edge, each stopping at the strip’s start. A strip can run on past its column to the window’s edge while its first card keeps to the column, loop without an end, and, when asked, move on by itself.
Everything a carousel holds is in the page and reachable without it: never put the only copy of something important in a slide.
Design examples
Section titled “Design examples”Text slides keep the same measure while their lengths differ.
<section class="specimen">
<ds-carousel label="Field journal observations" literal buttons><blockquote style="margin:0;min-height:calc(5 * var(--ds-unit))"><ds-text literal wrap>The room changes before anyone moves.</ds-text><ds-gap size="05"></ds-gap><ds-text literal size="small" tone="muted">North window, 08:10</ds-text></blockquote><blockquote style="margin:0;min-height:calc(5 * var(--ds-unit))"><ds-text literal wrap>Rain makes the courtyard sound larger.</ds-text><ds-gap size="05"></ds-gap><ds-text literal size="small" tone="muted">Courtyard, 11:45</ds-text></blockquote><blockquote style="margin:0;min-height:calc(5 * var(--ds-unit))"><ds-text literal wrap>By evening the walls have borrowed the sky.</ds-text><ds-gap size="05"></ds-gap><ds-text literal size="small" tone="muted">West room, 17:20</ds-text></blockquote></ds-carousel>
</section>
<style>
.specimen { width: 100%; min-width: 0; --cell: calc(20px * var(--sf, 1)); }
.specimen .line { width: 100%; }
.specimen .grow { flex: 1; min-width: 0; }
.specimen .muted { color: var(--ds-text-color-muted); }
.specimen .mono { --ds-typeface-regular: var(--ds-typeface-mono); font-variant-numeric: tabular-nums; }
.specimen .value { width: calc(4 * var(--cell)); text-align: right; }
.specimen .mark { display: inline-flex; align-items: flex-end; width: var(--cell); height: var(--ds-control-height); flex: 0 0 var(--cell); }
.specimen .columns { display: flex; flex-wrap: wrap; gap: var(--cell); }
.specimen .columns > * { min-width: 0; }
.specimen fieldset { border: 0; padding: 0; margin: 0; }
.specimen legend { padding: 0; }
.specimen label { display: flex; align-items: flex-end; gap: calc(var(--cell) / 2); min-height: var(--cell); }
.specimen ds-panel { height: calc(10 * var(--cell)); }
</style> Try it
Section titled “Try it”Swipe or scroll sideways, or use the buttons and the dots. Moving to another slide fires change.
<ds-carousel label="Colours" literal slide-width="12" buttons>
<figure style="margin:0;height:calc(var(--ds-unit) * 8);background:var(--ds-apple-green)"></figure>
<figure style="margin:0;height:calc(var(--ds-unit) * 8);background:var(--ds-zenith-blue)"></figure>
<figure style="margin:0;height:calc(var(--ds-unit) * 8);background:var(--ds-pink)"></figure>
<figure style="margin:0;height:calc(var(--ds-unit) * 8);background:var(--ds-orange)"></figure>
</ds-carousel> Without a slide width each slide is the carousel's own width. The last dot marks an end.
<ds-carousel label="What people say" literal end>
<blockquote style="margin:0;height:calc(var(--ds-unit) * 4)">It reads the way I think.</blockquote>
<blockquote style="margin:0;height:calc(var(--ds-unit) * 4)">Everything lines up, on every screen.</blockquote>
<blockquote style="margin:0;height:calc(var(--ds-unit) * 4)">The keyboard reaches every slide.</blockquote>
</ds-carousel> Use it
Section titled “Use it”<ds-carousel label="Colours" slide-width="12" buttons> <figure>…</figure> <figure>…</figure> <figure>…</figure></ds-carousel>Each element inside is a slide. Name the carousel for what the slides show with label, a translation key or, with literal, the words as written.
| Width | Slides |
|---|---|
No slide-width | The carousel’s own width, one at a time. |
slide-width="12" | Twelve squares each, as many in view as fit; the next one is cut at the edge. |
slide-width="auto" | Each slide as wide as it is: a strip of cards to browse. |
gap sets the squares between slides, one by default and 0 for none. A slide is as tall as the tallest one.
carousel.addEventListener("change", event => { console.log(event.detail.index); // the slide a person moved to});carousel.current = 2; // goes there, smoothly unless the reader asks for less motionchange fires when a person moves to another slide: by swiping, scrolling, a dot or a button. Setting current, or calling goTo(), next() or previous(), moves the carousel without firing it.
A strip
Section titled “A strip”slide-width="auto" gives each card its own width, several in view. Swipe or scroll sideways, or use the buttons; each card stops at the start.
<ds-carousel label="Features" literal slide-width="auto" no-dots buttons>
<ds-card href="#lists" tone="sharp-blue" width="12" height="16" media="center">
<span slot="label">Lists</span>
<span slot="title">Everything on one grid</span>
<span slot="media" style="display:block;width:calc(var(--ds-unit) * 8);height:calc(var(--ds-unit) * 10);border-radius:var(--ds-radius);background:var(--ds-zenith-blue)"></span>
</ds-card>
<ds-card href="#notes" tone="apple-green" width="20" height="16" media="bleed">
<span slot="label">Notes</span>
<span slot="title">Every item opens into a note</span>
<span slot="media" style="display:block;width:calc(var(--ds-unit) * 24);height:calc(var(--ds-unit) * 10);border-radius:var(--ds-radius);background:var(--ds-apple-green)"></span>
</ds-card>
<ds-card href="#people" tone="pink" width="12" height="16" media="center">
<span slot="label">People</span>
<span slot="title">Kept with others</span>
<span slot="media" style="display:block;width:calc(var(--ds-unit) * 8);height:calc(var(--ds-unit) * 10);border-radius:var(--ds-radius);background:var(--ds-pink)"></span>
</ds-card>
<ds-card href="#calendar" tone="ink" width="12" height="16" media="center">
<span slot="label">Calendar</span>
<span slot="title">Dates in the dark</span>
<span slot="media" style="display:block;width:calc(var(--ds-unit) * 8);height:calc(var(--ds-unit) * 10);border-radius:var(--ds-radius);background:var(--ds-sharp-blue)"></span>
</ds-card>
</ds-carousel> loop holds the cards three times over; autoplay moves on every five seconds, holds while you point, touch or focus, and always has a pause button.
<ds-carousel label="Features" literal slide-width="auto" loop autoplay>
<ds-card href="#lists" tone="sharp-blue" width="12" height="16" media="center">
<span slot="label">Lists</span>
<span slot="title">Everything on one grid</span>
<span slot="media" style="display:block;width:calc(var(--ds-unit) * 8);height:calc(var(--ds-unit) * 10);border-radius:var(--ds-radius);background:var(--ds-zenith-blue)"></span>
</ds-card>
<ds-card href="#notes" tone="apple-green" width="20" height="16" media="bleed">
<span slot="label">Notes</span>
<span slot="title">Every item opens into a note</span>
<span slot="media" style="display:block;width:calc(var(--ds-unit) * 24);height:calc(var(--ds-unit) * 10);border-radius:var(--ds-radius);background:var(--ds-apple-green)"></span>
</ds-card>
<ds-card href="#people" tone="pink" width="12" height="16" media="center">
<span slot="label">People</span>
<span slot="title">Kept with others</span>
<span slot="media" style="display:block;width:calc(var(--ds-unit) * 8);height:calc(var(--ds-unit) * 10);border-radius:var(--ds-radius);background:var(--ds-pink)"></span>
</ds-card>
<ds-card href="#calendar" tone="ink" width="12" height="16" media="center">
<span slot="label">Calendar</span>
<span slot="title">Dates in the dark</span>
<span slot="media" style="display:block;width:calc(var(--ds-unit) * 8);height:calc(var(--ds-unit) * 10);border-radius:var(--ds-radius);background:var(--ds-sharp-blue)"></span>
</ds-card>
</ds-carousel> <ds-carousel label="Work" slide-width="auto" no-dots bleed="end" buttons> <ds-card href="/lists" tone="sharp-blue" width="14" height="22" media="center">…</ds-card> <ds-card href="/portal" tone="ink" width="28" height="22" media="bleed">…</ds-card> <ds-card href="/money" tone="apple-green" width="14" height="22" media="center">…</ds-card></ds-carousel>| Option | What it does |
|---|---|
bleed | none (the default) keeps the strip in its box. end runs it on to the window’s end edge, the first card still on the box’s start, so nothing stands before the column. both runs it to both edges; cards slide out past the column’s start. |
loop | Holds the slides three times over, so the strip has no end either way. The copies are hidden from assistive technology and never focused. A looping carousel draws no dots. |
autoplay | Moves on a slide at a time by itself: empty for every five seconds, or a number of milliseconds (at least 1000). Without loop it goes back to the first slide after the last. See Autoplay. |
free | Slides freely, without stopping at slides. |
bleed, loop and autoplay are read when the element first draws; put a new element in its place to change them.
Accessibility
Section titled “Accessibility”- The element is a region described as a carousel and named by
label. Each slide is a group described as a slide and labelled “2 of 5”, unless the page labels it. - The dots are a group of buttons, the current one marked
aria-current; the arrow keys, Home and End move between them. The buttons are named “Previous slide” and “Next slide” and stop at the ends. - When the slide in view changes, a hidden live region says “3 of 5” politely.
- The track is an ordinary scroller: swiping and trackpads move it, and so do the arrow keys where the browser lets a scroller take focus.
- With reduced motion, a dot or a button jumps to its slide at once and the dots do not grow.
- Previous and next point back and on along the reading direction, right to left too.
All the words are the shared words, in every language.
Autoplay
Section titled “Autoplay”A carousel moves on by itself only when asked to, with autoplay. Then it always shows a pause button (named “Pause”, then “Play”), holds while the pointer is over it, a finger is on it or the keyboard is in it, holds while it is out of view or the page is hidden, and never moves for a reader who asks for reduced motion. That meets WCAG 2.2.2 (Pause, Stop, Hide). Use it for a strip that shows off, never for one that holds something a person must read or act on: a slide that leaves while someone reads it costs more than it shows.
| Attribute | Type | Default | What it does |
|---|---|---|---|
label | string | The shared word “Carousel” | The region’s name. |
literal | boolean | false | Show label as written, never looked up. |
current | number | 0 | The slide in view, from 0. |
buttons | boolean | false | Previous and next buttons beside the dots. |
no-dots | boolean | false | Leave the dots out. |
end | boolean | false | Draw the last dot as an end marker. |
slide-width | number or auto | The carousel’s width | Each slide’s width in whole squares; auto for slides of their own width. |
gap | number | 1 | Whole squares between slides. |
bleed | none, end, both | none | How far a strip runs past its box. |
loop | boolean | false | Hold the slides three times over, without an end. |
autoplay | empty or milliseconds | Off | Move on by itself, with a pause button. |
free | boolean | false | Slide freely, without stopping at slides. |
| Event | Detail | What it means |
|---|---|---|
change | { index, previous } | A person moved to another slide. |
| Part | What it is |
|---|---|
track | The scroller holding the slides. |
controls | The row of buttons and dots. |
previous, next, pause | The buttons. |
dots | The page dots. |
Measures
Section titled “Measures”Slides are the carousel’s width or whole squares wide, a whole number of squares apart. The controls stand one square below the track, a row one square high starting at its edge: each button in a one-square cell (the icon button is one control high), each dot one square. A carousel of one slide has no controls. Snapping is mandatory, one slide to a stop.
--ds-carousel-slide and --ds-carousel-gap hold the widths, for a product that sets them for a narrower screen, such as the column less two squares so the next slide shows at the edge.
import { Carousel } from "@0001-labs/design-system-1/DS1/react/carousel";
<Carousel label={t("Colours")} slideWidth={12} buttons onChange={index => setColour(index)}> {colours.map(colour => <Card key={colour.key} colour={colour} />)}</Carousel>Carousel renders ds-carousel with the slides in the light DOM and the region and its name in the markup, so the server’s HTML is already a named region. label is shown as given. dots={false} leaves the dots out; slideWidth="auto", bleed, loop, autoplay and free make it a strip. Register ds-carousel in the browser before hydration.