SwipiDocumentation

Guides

Dots

Pagination rendered from snapCount and selectedIndex, plus a sliding indicator that moves between them.

One dot per snap

Dots are rendered from snapCount, not from the length of your array. The carousel stops once the remaining slides fit the viewport, so five half-width slides give four snaps — and four dots.

React
<nav className="carousel__dots">
  {Array.from({ length: carousel.snapCount }, (_, index) => (
    <button
      type="button"
      className="carousel__dot"
      key={index}
      aria-label={`Go to slide ${index + 1}`}
      aria-current={index === carousel.selectedIndex}
      onClick={() => carousel.scrollTo(index)}
    />
  ))}
</nav>
Dots
.carousel__dots {
  display: flex;
  align-items: center;
  gap: 10px;
}

.carousel__dot {
  height: 12px;
  width: 12px;
  padding: 0;
  background-color: #d2d2d2;
  border: none;
  border-radius: 50%;
  cursor: pointer;
}

.carousel__dot[aria-current='true'] {
  background-color: #b70808;
}

A sliding indicator

Instead of recolouring the active dot, one element can travel between them. Pass the index down as a custom property and let CSS do the arithmetic — no per-dot measurement, and it animates on the compositor.

React
<span
  className="carousel__indicator"
  aria-hidden="true"
  style={{ '--index': carousel.selectedIndex }}
/>
Indicator
.carousel__dots {
  position: relative;
}

.carousel__indicator {
  position: absolute;
  left: 0;
  height: 12px;
  width: 12px;
  background-color: #b70808;
  border-radius: 50%;
  /* dot size + gap */
  transform: translateX(calc(var(--index) * 22px));
  transition: transform 300ms cubic-bezier(0.25, 1, 0.5, 1);
  pointer-events: none;
}

The step is the dot size plus the gap. Keep the indicator aria-hidden and pointer-events: none: it is decoration sitting over the buttons that do the work.

Dots and the first paint

snapCount is 0 until the slides are measured, which happens once the viewport is attached. Dots driven by it therefore appear a frame after the first paint rather than in the server output — render them from your own data if you need them in the HTML, as Server rendering explains.

@ Copyright 2026 Midstem. All rights reserved.

DocsMidstemGitHubLinkedin