If a carousel only has a single item, no controls and no pagination are
rendered.
The element around the slides has tabindex="0", so that the slides can be
scrolled by keyboard and not only stepped through with the controls.
Required CSS
The element with the -items-container class has to be a horizontal scroll
container, since the controls and the auto rotation move the carousel by
scrolling it:
.carousel-items-container {
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.carousel-items {
display: flex;
}
.carousel-item {
flex: 0 0 100%;
scroll-snap-align: center;
}
The items must not shrink. Scroll snapping is optional, but without it a
slide can come to rest half shown. Whether the movement is animated
depends on scroll-behavior. If you animate it, turn it off with a
@media (prefers-reduced-motion) media query.
Styling
The carousel has a data-active-index attribute, and data-paused while
the rotation is stopped. The visible slide has aria-current, its
pagination tab has aria-selected, and with loop={false} the previous and
next buttons are disabled at the ends.
The pause button changes its label, not its content. Put both icons into the
slot and use a CSS selector on data-paused to pick one:
<:pause label="Pause slide show" resume_label="Resume slide show">
<.icon name="pause" class="when-running" />
<.icon name="play" class="when-paused" />
</:pause>
.carousel:not([data-paused]) .when-paused,
.carousel[data-paused] .when-running {
display: none;
}
Localization
The build options carousel_roledescription, slide_roledescription,
pagination_label and pagination_slide_label, and the labels of the
:pause slot, default to English. They are announced by screen readers and
should be translated, as should the labels of the :previous and :next
slots.
Defaults
A default for the :pause, :previous or :next slot turns on rotation or
the previous and next buttons for every carousel of the build, and a call
cannot turn them off. If some carousels need them and others do not, build
the carousel a second time under another name: with other defaults, or set
no defaults and pass the slots at each call.
Maturity: Developing
The semantics follow the ARIA Authoring Practices. With pagination, the
pickers are a tablist and the slides are its tab panels. Without it, the
slides are groups.
Everything the pattern asks for is implemented. The level stays at
:developing because the API is new and has not been proven in production
yet.
Usage
<.carousel id="dog-carousel" label="Our Dogs">
<:previous label="Previous Slide">
<Heroicons.chevron_left />
</:previous>
<:next label="Next Slide">
<Heroicons.chevron_right />
</:next>
<:item label="1 of 3">
<.image
src="https://github.com/woylie/doggo/blob/main/assets/images/dog_1.webp?raw=true"
alt="A gray-muzzled dog in a camouflage coat and harness."
ratio="16:9"
/>
</:item>
<:item label="2 of 3">
<.image
src="https://github.com/woylie/doggo/blob/main/assets/images/dog_2.webp?raw=true"
alt="A small curly-haired white dog seen from the side."
ratio="16:9"
/>
</:item>
<:item label="3 of 3">
<.image
src="https://github.com/woylie/doggo/blob/main/assets/images/dog_3.webp?raw=true"
alt="A large cream-colored dog on a leash, looking up."
ratio="16:9"
/>
</:item>
</.carousel>
This component needs the Doggo.Carousel JavaScript hook. See
Phoenix LiveView Hooks for
registering it.
Keyboard
Left and Right - previous or next slide, with the focus on a
pagination tab. With loop they wrap, without it they stop at the ends.
Home and End - first or last slide, with the focus on a pagination
tab.
The pagination is a single tab stop. The arrow keys need the colocated hook.