Embla Carousel Variable-Width Chips with Scroll Progress — Free Snippet

Embla Carousel Variable-Width Chips with Scroll Progress · Carousels · Plain HTML, CSS & JS · Live preview

What's included

Features

Content-sized slides
flex: 0 0 auto, measured by Embla.
Momentum scrolling
dragFree for a native feel.
Trimmed ends
No blank space after the last chip.
Scroll progress bar
Driven by scrollProgress().
Edge-aware arrows
Disabled when there is nothing more.
Mouse-wheel mapping
Vertical wheel scrolls the row, page scroll resumes at the ends.
Focus tracking
Tabbing reveals off-screen chips.
Announced selection
A polite live region.

About this UI Snippet

Embla Carousel for Chip Rows — Variable Widths, Momentum and a Progress Bar

Screenshot of the Embla Carousel Variable-Width Chips with Scroll Progress snippet rendered live

Carousels aren't only for big images. A row of filter chips or category pills that overflows on mobile is the same problem at a smaller scale: it needs to scroll smoothly, show that there is more, and stay usable with a mouse, a trackpad, touch and a keyboard. Embla handles all of that without forcing a fixed slide width.

Slides can be any width

Each chip is flex: 0 0 auto, so it sizes to its own text. Embla measures every slide individually and builds snaps from real sizes, so there is no configuration for variable widths. Gaps come from gap on the container.

Drag-free with trimmed ends

dragFree: true lets the row glide with momentum and stop anywhere, which feels like a native scroller. containScroll: 'trimSnaps' stops at the real ends so there is never empty space after the last chip.

A progress bar that shows how much is left

scrollProgress() returns 0 at the start and 1 at the end of the scrollable range. The bar's scaleX follows it on every scroll event. It is clamped because the value briefly goes past 0 or 1 when you drag beyond an edge.

Wheel support without trapping the page

A horizontal carousel ignores vertical mouse wheels by default, which makes it feel broken to desktop users. The snippet maps vertical wheel movement to scrollPrev and scrollNext, but only while the row can still move in that direction. At either end, the event isn't prevented and the page scrolls normally.

Keyboard focus follows

Tabbing onto a chip that is off-screen scrolls it into view, using the engine's slide registry to find the snap that contains that chip. Selection uses aria-selected, and the chosen topic is announced in a live region.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Give this snippet to an AI assistant like Claude and ask it to explain why variable widths need no configuration and why the wheel handler only prevents default while the row can move. Ask it to add fading edge shadows that appear only when there is more content, multi-select filters with a clear button, or syncing the selected chip with the URL. It can also review whether listbox and option are the right roles for your filter.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a horizontally scrolling row of topic filter chips with Embla Carousel v8 (loaded from a CDN as UMD) in plain HTML, CSS and JavaScript.

Requirements:
- Sixteen chips with a topic name and article count, each sized to its own content, with a CSS gap between them.
- Drag-free momentum scrolling, aligned to the start, with the row stopping at its real ends.
- A thin progress bar under the row driven by the carousel's scroll progress, clamped between 0 and 1.
- Previous and next arrow buttons that disable at the ends.
- Map vertical mouse-wheel movement to scrolling the row, but let the page scroll once the row reaches an end.
- Clicking a chip selects it (aria-selected) and announces the chosen topic in a polite live region.
- When a chip receives keyboard focus, scroll it into view.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="evw">
  <div class="evw-head">
    <h2>Browse by topic</h2>
    <div class="evw-arrows">
      <button type="button" id="evwPrev" aria-label="Scroll topics left">‹</button>
      <button type="button" id="evwNext" aria-label="Scroll topics right">›</button>
    </div>
  </div>
  <div class="evw-viewport" id="evwViewport">
    <div class="evw-container" id="evwList" role="listbox" aria-label="Topics"></div>
  </div>
  <div class="evw-progress" aria-hidden="true"><div class="evw-bar" id="evwBar"></div></div>
  <p class="evw-out" id="evwOut" aria-live="polite">Showing: All topics</p>
</div>

Step by step

How to Use

  1. 1
    Load EmblaInclude embla-carousel.umd.js from the CDN.
  2. 2
    Paste the snippetA row of topic chips with arrows and a progress bar renders.
  3. 3
    Scroll the rowDrag, flick, use the arrows or the mouse wheel.
  4. 4
    Pick a topicClick a chip; the selection is announced below.
  5. 5
    Tab throughOff-screen chips scroll into view as they receive focus.

Real-world uses

Common Use Cases

Category filters
Blog, shop or docs topic pills.
Tag rows
Horizontal tag clouds on mobile.
Date pickers
Scrollable day chips in booking flows.
Story or avatar rows
Mixed-width items in a strip.
Toolbars
Overflowing actions on small screens.
Related: Chip Filter
A wrapping, non-scrolling alternative: Chip Filter.
Related: Drag Scroll Row
A dependency-free drag row: Drag Scroll Row.

Got questions?

Frequently Asked Questions

Yes. Size each slide with CSS, for example flex: 0 0 auto, and Embla measures every slide to build its snaps. No special option is needed.

Call scrollProgress() in a scroll event handler. It returns 0 at the start and 1 at the end; clamp it because it can overshoot slightly while dragging past an edge.

Listen for wheel events on the viewport with passive: false, and when the vertical delta is larger, call scrollNext or scrollPrev and prevent the default. Only prevent it while the carousel can still move, so the page can scroll at the ends.

Normal dragging snaps to the nearest slide when released. dragFree lets the carousel keep gliding with momentum and stop anywhere, which suits chip rows and long lists.

Both work in Embla v8. Container gap is simpler for variable-width content; padding on slides with a negative container margin is the older pattern and is also supported.