Open sourcing carousel component
March 09, 2024

You know what they say, "You're not really a UI developer until you've built an image carousel", or something like that. Well, hand me my title cause I've built one and open sourced it 🎉

Okay, full disclosure, I didn't set out to build an carousel. For my recent blog post on building Goodreads Raycast extension I wanted a compact yet immersive way to showcase the screenshots. Though I found some great open-source carousels, like react-slick and SwiperJS, I was looking for a stacked card transition effect with minimal bundle-size footprint, so decided to build my own.

Weighing in at just 2.3KB (minified + gzipped), introducing react-card-stack-carousel

Usage

npm install react-card-stack-carousel
import React from "react";
import { StackedCarousel } from "react-card-stack-carousel";
import "react-card-stack-carousel/styles/styles.css"; // import base styles
 
export default function App() {
    // specify container height
    const containerHeight = 250;
 
    return (
        <main className="container">
            <StackedCarousel height={containerHeight}>
                <div className="sample-card bg-color-1">0</div>
                <div className="sample-card bg-color-2">1</div>
                <div className="sample-card bg-color-3">2</div>
            </StackedCarousel>
        </main>
    );
}

Getting ready to open source

The first version of carousel was tailor made for my blog, liberal on styling and API choices. It only supported images with custom plumbing to load and render. To open source it, I had to reconsider a few design choices to make it versatile and configurable. We'll go over some of the steps I took to make it open source ready.

1
Setting up the repository and dev environment

Created a new Github repo react-card-stack-carousel to host the code and used Vite for local development with the following config.

vite.config.js
import path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
 
// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        react({
            include: "**/*.jsx",
        }),
    ],
    resolve: {
        alias: {
            "react-card-stack-carousel": path.resolve(__dirname, "../"),
        },
    },
});
2
Making the carousel versatile

Revised the carousel component to support any type of content, not just image. To allow for easy style overrides, adopted a variant of Object-based styling used in fluentui Updated the transitions to be config driven to allow for easy customization.
You can look at the documentation for all the customization options.

3
Documentation

Getting things to work was only half the job. Next steps included writing clear documentation with demos, examples and instructions on how to use and customize the carousel.

4
Building and publishing to NPM

Added a build step to support different module formats like CommonJS and updated the package with necessary steps to publish.

vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
 
export default defineConfig({
    plugins: [react()],
    build: {
        lib: {
            entry: "./index.js",
            name: "react-card-stack-carousel",
            fileName: "index",
            formats: ["cjs"],
        },
        rollupOptions: {
            external: ["react"],
            output: {
                globals: {
                    react: "React",
                },
            },
        },
    },
});

Handling responsive design

Since the carousel requires a mandatory height property, had to device a way make it adaptive without burdening the consumer with the task of responding to changing screen dimensions.

The height prop supports Tailwind CSS based breakpoint system to enable responsive design.

<!-- 200px on mobile (default), 500px on medium screens and 750px on large screens -->
<StackedCarousel height="200 md:500 lg:750">{...}</StackedCarousel>

Under the hood it uses window.matchMedia and some regex matching. Read through the code if you're curious or have suggestions.

Implementing stacked card-swipe effect

The stacked card effect is achieved using CSS 3D transforms. Building the carousel also served as a good refresher on the topic. Getting the effect just right was the most frustrating and satisfying part of the project.

Based on the position of card in the stack, we compute the scale, opacity and vertical offset to create the stacked effect.

CarouselItem.jsx
// values passed down as props
const transition = `opacity ${duration}s ${easing}, transform ${duration}s ${easing}`;
const transform = `
    translate3d(${tX}, ${tY}, ${tZ})
    rotateX(${rotateX}deg)
    scale(${scale})
`;
 
const computedStyle = {
    opacity,
    transition,
    transitionDelay: `${delay}s, ${delay}s`,
    transform,
    zIndex,
};
 
return <div style={computedStyle}>{children}</div>;

Fin.

Thank you reading through and if you're looking a for a lightweight, image carousel for your next project, give react-card-stack-carousel a try and let me know what you think.