# Smooth Dot Indicators with Embla Carousel and CSS color-mix()

Published: Fri, Mar 06, 2026

The interview gallery on [ibrewmyown.coffee](https://ibrewmyown.coffee/interviews/fatih-arslan) uses image carousels with pagination dots. Standard pagination dots using Embla Carousel toggle between active and inactive, but I wanted them to respond to scroll progress: as you drag, the current indicator fades out while the next one fades in.

## Implementation

[Embla Carousel](https://www.embla-carousel.com/) exposes scroll progress, making this possible:

```typescript
const onScroll = (): void => {
  const snapList = emblaApi.scrollSnapList();
  const progress = emblaApi.scrollProgress();

  if (snapList.length < 2) return;

  let lower = snapList.length - 2;
  for (let i = 0; i < snapList.length - 1; i++) {
    if (progress >= snapList[i] && progress <= snapList[i + 1]) {
      lower = i;
      break;
    }
  }

  const upper = lower + 1;
  const range = snapList[upper] - snapList[lower];
  const t = range === 0 ? 0 : (progress - snapList[lower]) / range;

  dotNodes.forEach((_, i) => {
    if (i === lower) setDotProgress(i, 1 - t);
    else if (i === upper) setDotProgress(i, t);
    else setDotProgress(i, 0);
  });
};
```

As `t` interpolates from 0 to 1, a CSS custom property `--dot-progress` is set for each dot. This single value drives two animations: width expansion and color interpolation.

## Width and Color

The dot width expands as you approach it:

```css
width: calc(var(--spacing) * 2 + var(--spacing) * 3 * var(--dot-progress, 0));
```

And the color blends between two states using `color-mix()`:

```css
background-color: color-mix(
  in srgb,
  var(--color-neutral-600) calc(var(--dot-progress, 0) * 100%),
  var(--color-neutral-300)
);
```

This approach preserves contrast, keeping the active dot darkest and the inactive lightest. The browser handles both interpolations without additional JS overhead.

Hat tip to [Derek Briggs](https://x.com/PixelJanitor/status/2029700875006922763) for the color fade idea: "What if the current active indicator faded out the primary color during the drag and the next active faded in so that the most active item had the most contrast always?"
