A scrollable, and swipeable carousel, even on desktop (complete with snapping, friction, rubber-banding and overscroll). Inspired by a component made at Finary, a one-stop shop for wealth management. Play with the component, and try changing the card size.
Of course you can scroll the regular way, but you can also drag to swipe the carousel. On most browsers (cough cough, Safari...) dragging to swipe will respect the css scroll-snap-align. When you drag to swipe, we use a custom momentum scrolling implementation when needed (desktop browsers). The greater the velocity, the further the carousel will scroll.
When dragging to swipe, if you give it enough velocity the carousel will overscroll, with a rubber-banding effect, similar to the one you get on touch devices by default. For this to work, we calculate a velocity based on how fast you are moving your mouse and apply a deceleration factor. When yous scrolled to the start of the end of the carousel, you can also trigger the rubber-banding effect by dragging the carousel even more.
The carousel can be paginated, using the dedicated buttons, pagination is based on the scroll-snap-align set in css. Pagination accounts for the fade mask if any, or whatever offset is returned by the boundaryOffset, this allows to ensure the next item is always fully visible, instead of being partially masked, ensuring better pagination.
You can play withe the demo controls to change the snapping. Thanks to the css styles, the carousel items will snap naturally when performing a regular scroll. But when you drag to scroll on desktop, this behavior is not a given. You have to implement it yourself by adjusting the deceleration factor for the velocity, so that the velocity reaches 0 towards the snap point. Snapping is also respected when using pagination or whn tabbing.
Full support for tabbing through the carousel items, provided the items contain tabbable content. Here again, when tabbing through, the carousel fully enforces the desired scroll-snap-align and makes sure the tabbed item is fully visible instead of being partially hidden by the mask, or if you choose to render the prev / next buttons on top of the carousel, you cqn provide a custom boundaryOffset function to account for these.
Most scroll fades only animate the opacity when you reach the edges of the scroll area. Instead of doing this, I decided to animate the length of the mask based on the remaining scroll distance. This way, the fading effect is more natural, and the transition is smoother. As you approach the edges, the mask smoothly updates.
{/* Provides context to the carousel components */}
<Carousel.Root>
{/* The scrollable area */}
<Carousel.Viewport>
{/* The container for the items */}
<Carousel.Content>
{/* A carousel item */}
<Carousel.Item />
<Carousel.Item />
<Carousel.Item />
</Carousel.Content>
</Carousel.Viewport>
{/* The pagination buttons */}
<Carousel.PrevPage />
<Carousel.NextPage />
</Carousel.Root>While implementing the basic version of the carousel is easy, thanks to modern css, implementing momentum scrolling with snapping and overscroll / rubber-banding on desktop isn’t trivial. Maybe I’ll try to enable infinite scrolling at some point, but for now, this is a good start.