A TailwindCSS carousel that can’t get any simpler
Building a carousel component from scratch may sound scary at first but with TailwindCSS and HeadlessUI, it’s almost trivial. In this example, I am using React but both tools can also be used with Vue. You can also check out the finished example on codesandbox. This example is written in TypeScript
Preparation
If you haven’t yet, make sure your TailwindCSS project has HeadlessUI installed. For React, simply npm install @headlessui/react
. In my example, we are also using classnames which is a handy package to conditionally add CSS class names to elements.
Implementation
export default function App() {
const INTERVAL_LENGTH = 2000;
const AUTOPLAY = true;
const items = [
<h1 className="my-8 text-5xl font-bold tracking-tighter text-slate-800">
My <span className="text-pink-600">first</span> headline
</h1>,
<h1 className="my-8 text-5xl font-bold tracking-tighter text-slate-800">
My <span className="text-blue-600">second</span> headline
</h1>,
<h1 className="my-8 text-5xl font-bold tracking-tighter text-slate-800">
My <span className="text-green-600">last</span> headline
</h1>
] as ReactNode[];
const [currentItem, setCurrentItem] = useState(0);
const prev = () =>
setCurrentItem((curr) => (curr === 0 ? items.length - 1 : curr - 1));
const next = () =>
setCurrentItem((curr) => (curr === items.length - 1 ? 0 : curr + 1));
useEffect(() => {
if (!AUTOPLAY) return;
const interval = setInterval(next, INTERVAL_LENGTH);
return () => clearInterval(interval);
}, []);
return (...)
}
Explanation
- items: A list of your carousel items. These can be any JSX/TSX element that you want to cycle through.
[currentItem, setCurrentItem]
: We are keeping track of the currently displayed item in the carouselprev
andnext
: Two functions to jump back or ahead in the carousel. The condition inside each of the functions makes sure that going previous on the first item jumps to the last item and going next on the last item jumps to the first item. This is a common carousel behaviour.- Inside the
useEffect
we are initialising the auto-play behaviour of the carousel. You can remove this (or setAUTOPLAY
to false) if you only want the carousel to go back/forward on user input. TheINTERVAL_LENGTH
is in milliseconds
Rendering the carousel
The markup of the carousel itself is the following
return (
<section className="relative h-[150px] w-full overflow-hidden text-center">
{items.map((item, index) => (
<Transition
key={index}
show={currentItem === index}
enter="transition-opacity duration-1000"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-1000"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="absolute w-full">{item}</div>
</Transition>
))}
{/* Controls */}
<div className="absolute w-full h-full items-center flex justify-between">
<button
className="m-4 h-8 w-8 rounded-full bg-slate-200"
onClick={prev}
>
{"<"}
</button>
<button
className="m-4 h-8 w-8 rounded-full bg-slate-200"
onClick={next}
>
{">"}
</button>
</div>
{/* Indicator */}
<div className="absolute top-[125px] flex w-full justify-center gap-4">
{items.map((_, index) => (
<div
key={index}
className={classNames("h-[4px] w-1/12 duration-1000", {
"bg-slate-300": index !== currentItem,
"bg-slate-800": index === currentItem
})}
/>
))}
</div>
</section>
);
Explanation
- The
<section>
tag wraps the entire carousel inside arelative
container so that the individual carousel items can be stacked on top of each other usingabsolute
- Each carousel item is wrapped around a HeadlessUI
<Transition>
component to enable the transition animation. The example above uses a fade animation but you could easily do a slide animation as such:
<Transition
key={index}
show={currentItem === index}
enter="transition ease-out duration-1000"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in duration-1000"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<div className="absolute w-full">{item}</div>
</Transition>
- The controls are rendered on top of the carousel item and by using
flex justify-between items-center
we can put the back and forward buttons on each end of the carousel. The buttons simply call theprev
andnext
functions respectively. - The indicators are just slim, gray containers that are rendered darker when its respective index is equal to the current item. The
classNames
package makes this logic syntactically easier to grasp.
And there you have it! Easy autoplaying carousel with indicators without additional JS code. Please leave some feedback if you have any.