xoong
Menu · Carousel
DocsControlsCarousel

Carousel

<xo-carousel> is a row of slides, its children, that you move through. It is made on the platform: the slides sit in a scroll container with CSS scroll snap, so touch swiping, the wheel and momentum are the browser's own, and the buttons only ask it to scroll. It follows the WAI-ARIA carousel pattern, and a slide that is out of view cannot be reached with Tab. These are the forms it covers, one element each:

Cards, several at a time

items sets how many are visible; set --xo-carousel-items in CSS to change it with the screen width. counter shows the position, and the arrows stop at the ends (or wrap with loop).

Digestive check

Advice on stomach, bowel and liver symptoms.

Book

Painless endoscopy

Stomach and bowel, with sedation.

Book

H. pylori test

A sample taken during the endoscopy.

Book

Abdominal ultrasound

Done together with the doctor.

Book

Health packages

Checks with offers.

Book
<xo-carousel class="service-cards" label="Services" counter>
  <article class="card"><h3>Digestive check</h3><p>Advice on stomach, bowel and liver symptoms.</p><a href="#book">Book</a></article>
  <article class="card"><h3>Painless endoscopy</h3><p>Stomach and bowel, with sedation.</p><a href="#book">Book</a></article>
  <article class="card"><h3>H. pylori test</h3><p>A sample taken during the endoscopy.</p><a href="#book">Book</a></article>
  <article class="card"><h3>Abdominal ultrasound</h3><p>Done together with the doctor.</p><a href="#book">Book</a></article>
  <article class="card"><h3>Health packages</h3><p>Checks with offers.</p><a href="#book">Book</a></article>
</xo-carousel>
.service-cards { --xo-carousel-items: 1; }
@media (min-width: 40rem) { .service-cards { --xo-carousel-items: 2; } }
@media (min-width: 60rem) { .service-cards { --xo-carousel-items: 3; --xo-carousel-gap: 1.5rem; } }

A banner or testimonials, moving by itself

One slide at a time with dots and autoplay (milliseconds). It adds a pause button, pauses while the pointer or the focus is inside, does not run under prefers-reduced-motion, and the slides are announced politely only when it is not moving.

<xo-carousel class="banner" label="What people say" dots autoplay="6000" loop>
  <blockquote><p>The form just worked: validation, errors and the draft came from the OpenAPI document.</p><footer>Ada, platform team</footer></blockquote>
  <blockquote><p>We deleted our table component and kept the markup.</p><footer>Grace, admin app</footer></blockquote>
  <blockquote><p>Strict CSP on day one, no exceptions to write.</p><footer>Barbara, security</footer></blockquote>
</xo-carousel>

A slide with data-thumb gives its picture to the button that goes to it, in place of a dot. Keys on the picture area: Left and Right, Home and End.

Clay hills Green valley Night over the ridge
<xo-carousel label="Gallery" dots>
  <img src="img/gallery-1.svg" alt="Clay hills" data-thumb="img/gallery-1.svg">
  <img src="img/gallery-2.svg" alt="Green valley" data-thumb="img/gallery-2.svg">
  <img src="img/gallery-3.svg" alt="Night over the ridge" data-thumb="img/gallery-3.svg">
</xo-carousel>

Slides from data

The slides can come from the data layer: put xo-each on the slide. The carousel follows its children as they arrive and change.

{name}

{email}

{role}
<xo-view get="listPeople" p-limit="6">
  <xo-carousel class="service-cards" label="People" items="2" counter no-arrows>
    <article class="card" xo-each="items"><h3>{name}</h3><p>{email}</p><span class="badge">{role}</span></article>
  </xo-carousel>
</xo-view>

Arrows at the sides

The arrows, dots and counter are children of the carousel's controls row. To lay them out another way, make the row and the arrows' box transparent to layout (display: contents) and place each piece in the carousel's own grid. This is how this site draws them:

xo-carousel { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; column-gap: 0.75rem; align-items: center; }
xo-carousel .xo-carousel-controls, xo-carousel .xo-carousel-nav { display: contents; }
xo-carousel .xo-carousel-track { grid-column: 2; grid-row: 1; }
xo-carousel .xo-carousel-prev { grid-column: 1; grid-row: 1; }
xo-carousel .xo-carousel-next { grid-column: 3; grid-row: 1; }
xo-carousel .xo-carousel-dots { grid-column: 2; grid-row: 2; justify-self: center; }
xo-carousel .xo-carousel-count { grid-column: 2; grid-row: 2; justify-self: end; }
APIWhat it does
itemsSlides visible at once (default 1; 1.15 lets the next one peek)
dots, counter, no-arrowsThe position buttons (or thumbnails), "1 / 5", hide the arrows
loop, autoplayNext at the end returns to the first; move on by itself every n milliseconds
index, count, next(), prev(), goTo(i), play(), pause()Control it from code; index is the first visible slide
xo-carousel-changeFires with { index, count }

Not included on purpose: crossfade and 3D styles, an endless loop made of cloned slides, and a continuously scrolling logo strip. They need a different mechanism than scroll snap, and the last two are bad for people who use a keyboard or a screen reader; a CSS animation does the logo strip well.