Carousel

Renders a carousel for presenting a sequence of items, such as images or text.

Read more Read less

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.