Building a Smooth, Infinite Carousel in Kobweb
If you’ve ever tried to build a carousel in the browser, you’ve probably run into some of these problems:
- It only scrolls if there are “enough” items.
- There’s an awkward jump when the carousel resets.
- You end up running a forever loop in JavaScript / coroutines.
- Background tabs get throttled and everything stutters.
In Kobweb, you don’t need any of that. You can get a smooth, infinite, GPU‑accelerated carousel with:
- Pure CSS transform animation (no manual scrolling)
- A small, reusable
InfiniteCarouselcomposable - Silk
CssStyleand keyframes
This article walks through that pattern step‑by‑step.
1. The Core Idea: Modern “Marquee” with Transforms
The modern carousel pattern is:
- Duplicate your content exactly once (so you have
[items][items]). - Animate the whole track from
translateX(0%)totranslateX(-50%). - Because the second half is identical to the first, when the animation loops, it’s visually seamless.
No timers, no scroll APIs, just a transform animation that the browser can optimize on the GPU.
In Kobweb + Silk, we model this as:
- A viewport:
Boxthat hides overflow. - A track:
Boxthat lays out items in one row and runs the animation. - A generic composable:
InfiniteCarousel<T>that you can feed any item type.
2. Define the Keyframes
First, we define the scroll keyframes: from 0% to -50%.
This tells the browser:
Take the whole track and move it left until half of it has slid out of view.
3. Style the Track with Silk
Next, we build a track style that:
- Uses inline‑flex so items sit in a single row.
- Has a gap between items.
- Runs our keyframe animation forever.
This is the heart of the marquee behavior.
Optional: Pause on Hover
If you want the carousel to pause when the user hovers it, add a hover block:
Note we only change playState, not duration. That avoids animation “jumps.”
4. Style the Viewport
The viewport is just a flex container that clips overflow so we only see one “window” onto the infinite track:
5. Implement the Generic InfiniteCarousel<T>
Now we can write a reusable composable that:
- Accepts any item list (
List<T>) - Handles the “too few items” case
- Duplicates the content
- Emits your custom
itemContent
A couple of important details:
- Small lists: When there are fewer than 4 items, we expand the list by repeating until it feels “long enough” to scroll.
- Duplication: We render
baseItems + baseItemsso the keyframes can move from0%to-50%and loop perfectly.
6. Using the Carousel with Your Own Cards
Because InfiniteCarousel is generic, you just pass in your data and a composable that knows how to render a single item.
For example, with your Project model and ProjectCard:
7. Recap
We’ve built a carousel that:
- Scrolls infinitely, regardless of how many items you have.
- Is powered by CSS transforms and Silk keyframes, not manual JS logic.
- Is exposed as a simple, reusable
InfiniteCarousel<T>composable. - Plays nicely with Kobweb/Silk patterns for styling and hover effects.