MC Carousel

A component for cycling through a series of content.

Overview

The mc-carousel is a flexible carousel component for the MicroClub UI library. Built on embla-carousel-react, it provides smooth, performant scrolling through slides with navigation controls.

Preview

Loading...
import {  McCarousel,  McCarouselContent,  McCarouselItem,  McCarouselNext,  McCarouselPrevious,} from '../ui/mc-carousel';export default function McCarouselDemo() {  return (    <McCarousel className="w-full max-w-xs">      <McCarouselContent>        {Array.from({ length: 5 }).map((_, index) => (          <McCarouselItem key={index}>            <div className="flex aspect-square items-center justify-center p-6">              <span className="text-4xl font-semibold">{index + 1}</span>            </div>          </McCarouselItem>        ))}      </McCarouselContent>      <McCarouselPrevious />      <McCarouselNext />    </McCarousel>  );}

Add to Your Project

Deploy the mc-carousel directly to your components/ui directory:

npx mcoli-ui@latest add mc-carousel
pnpm dlx mcoli-ui@latest add mc-carousel
yarn dlx mcoli-ui@latest add mc-carousel
bunx mcoli-ui@latest add mc-carousel

Manual Setup

If you prefer manual control:

Install the core dependencies
npm install embla-carousel-react lucide-react
pnpm add embla-carousel-react lucide-react
yarn add embla-carousel-react lucide-react
bun add embla-carousel-react lucide-react
Copy the source code
'use client';import * as React from 'react';import useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react';import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';import { cn } from '@/lib/utils';import { Button } from '@/components/ui/button';type McCarouselApi = UseEmblaCarouselType[1];type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;type CarouselOptions = UseCarouselParameters[0];type CarouselPlugin = UseCarouselParameters[1];type CarouselProps = {  opts?: CarouselOptions;  plugins?: CarouselPlugin;  orientation?: 'horizontal' | 'vertical';  setApi?: (api: McCarouselApi) => void;};type CarouselContextProps = {  carouselRef: ReturnType<typeof useEmblaCarousel>[0];  api: ReturnType<typeof useEmblaCarousel>[1];  scrollPrev: () => void;  scrollNext: () => void;  canScrollPrev: boolean;  canScrollNext: boolean;} & CarouselProps;const McCarouselContext = React.createContext<CarouselContextProps | null>(null);function useMcCarousel() {  const context = React.useContext(McCarouselContext);  if (!context) {    throw new Error('useMcCarousel must be used within a <McCarousel />');  }  return context;}function McCarousel({  orientation = 'horizontal',  opts,  setApi,  plugins,  className,  children,  ...props}: React.ComponentProps<'div'> & CarouselProps) {  const [carouselRef, api] = useEmblaCarousel(    {      ...opts,      axis: orientation === 'horizontal' ? 'x' : 'y',    },    plugins  );  const [canScrollPrev, setCanScrollPrev] = React.useState(false);  const [canScrollNext, setCanScrollNext] = React.useState(false);  const onSelect = React.useCallback((api: McCarouselApi) => {    if (!api) return;    setCanScrollPrev(api.canScrollPrev());    setCanScrollNext(api.canScrollNext());  }, []);  const scrollPrev = React.useCallback(() => {    api?.scrollPrev();  }, [api]);  const scrollNext = React.useCallback(() => {    api?.scrollNext();  }, [api]);  const handleKeyDown = React.useCallback(    (event: React.KeyboardEvent<HTMLDivElement>) => {      if (event.key === 'ArrowLeft') {        event.preventDefault();        scrollPrev();      } else if (event.key === 'ArrowRight') {        event.preventDefault();        scrollNext();      }    },    [scrollPrev, scrollNext]  );  React.useEffect(() => {    if (!api || !setApi) return;    setApi(api);  }, [api, setApi]);  React.useEffect(() => {    if (!api) return;    onSelect(api);    api.on('reInit', onSelect);    api.on('select', onSelect);    return () => {      api?.off('select', onSelect);    };  }, [api, onSelect]);  return (    <McCarouselContext.Provider      value={{        carouselRef,        api: api,        opts,        orientation: orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),        scrollPrev,        scrollNext,        canScrollPrev,        canScrollNext,      }}    >      <div        onKeyDownCapture={handleKeyDown}        className={cn('relative', className)}        role="region"        aria-roledescription="carousel"        data-slot="carousel"        {...props}      >        {children}      </div>    </McCarouselContext.Provider>  );}function McCarouselContent({ className, ...props }: React.ComponentProps<'div'>) {  const { carouselRef, orientation } = useMcCarousel();  return (    <div ref={carouselRef} className="overflow-hidden   " data-slot="carousel-content">      <div        className={cn('flex', orientation === 'horizontal' ? '' : '-mt-4 flex-col', className)}        {...props}      />    </div>  );}function McCarouselItem({ className, children, ...props }: React.ComponentProps<'div'>) {  const { orientation } = useMcCarousel();  return (    <div      role="group"      aria-roledescription="slide"      data-slot="carousel-item"      className={cn(        ' shrink-0 grow-0 basis-full',        orientation === 'horizontal' ? ' ' : '',        className      )}      {...props}    >      {/* Card shell matching Figma "Wrapper" spec exactly */}      <div        data-slot="carousel-item-card"        className="flex h-full w-full flex-col items-center justify-center gap-[8.14px] rounded-xl border border-border bg-card  text-card-foreground shadow"      >        {children}      </div>    </div>  );}function McCarouselPrevious({  className,  variant = 'outline',  size = 'icon-sm',  ...props}: React.ComponentProps<typeof Button>) {  const { orientation, scrollPrev, canScrollPrev } = useMcCarousel();  return (    <Button      data-slot="carousel-previous"      variant={variant}      size={size}      className={cn(        'absolute size-8.5 touch-manipulation rounded-full border border-border bg-card p-2.5 shadow-[0px_0.81px_2.44px_0px_rgba(0,0,0,0.1)]',        orientation === 'horizontal'          ? 'inset-y-0 -left-12 my-auto'          : '-top-12 -right-[-140px] rotate-90',        className      )}      disabled={!canScrollPrev}      onClick={scrollPrev}      {...props}    >      <ChevronLeftIcon className="cn-rtl-flip" />      <span className="sr-only">Previous slide</span>    </Button>  );}function McCarouselNext({  className,  variant = 'outline',  size = 'icon-sm',  ...props}: React.ComponentProps<typeof Button>) {  const { orientation, scrollNext, canScrollNext } = useMcCarousel();  return (    <Button      data-slot="carousel-next"      variant={variant}      size={size}      className={cn(        'absolute size-[34px] touch-manipulation rounded-full border-[1px] border-border bg-card p-[10px] ',        orientation === 'horizontal'          ? 'inset-y-0 -right-12 my-auto'          : '-bottom-12 -right-[-140px]   rotate-90',        className      )}      disabled={!canScrollNext}      onClick={scrollNext}      {...props}    >      <ChevronRightIcon className="cn-rtl-flip" />      <span className="sr-only">Next slide</span>    </Button>  );}export {  type McCarouselApi,  McCarousel,  McCarouselContent,  McCarouselItem,  McCarouselPrevious,  McCarouselNext,  useMcCarousel,};
Copy the example
import {  McCarousel,  McCarouselContent,  McCarouselItem,  McCarouselNext,  McCarouselPrevious,} from '../ui/mc-carousel';export default function McCarouselDemo() {  return (    <McCarousel className="w-full max-w-xs">      <McCarouselContent>        {Array.from({ length: 5 }).map((_, index) => (          <McCarouselItem key={index}>            <div className="flex aspect-square items-center justify-center p-6">              <span className="text-4xl font-semibold">{index + 1}</span>            </div>          </McCarouselItem>        ))}      </McCarouselContent>      <McCarouselPrevious />      <McCarouselNext />    </McCarousel>  );}

Usage

import {
  McCarousel,
  McCarouselContent,
  McCarouselItem,
  McCarouselPrevious,
  McCarouselNext,
} from '@/components/ui/mc-carousel';

export default function Example() {
  return (
    <McCarousel className="w-full max-w-xs">
      <McCarouselContent>
        {Array.from({ length: 5 }).map((_, index) => (
          <McCarouselItem key={index}>
            <div className="flex aspect-square items-center justify-center p-6">
              <span className="text-4xl font-semibold">{index + 1}</span>
            </div>
          </McCarouselItem>
        ))}
      </McCarouselContent>
      <McCarouselPrevious />
      <McCarouselNext />
    </McCarousel>
  );
}

Examples

<McCarousel className="w-full max-w-xs">
  <McCarouselContent>
    {Array.from({ length: 5 }).map((_, index) => (
      <McCarouselItem key={index}>
        <div className="flex aspect-square items-center justify-center p-6">
          <span className="text-4xl font-semibold">{index + 1}</span>
        </div>
      </McCarouselItem>
    ))}
  </McCarouselContent>
  <McCarouselPrevious />
  <McCarouselNext />
</McCarousel>

Vertical Orientation

<McCarousel orientation="vertical" className="w-full max-w-xs">
  <McCarouselContent>
    {Array.from({ length: 5 }).map((_, index) => (
      <McCarouselItem key={index}>
        <div className="flex aspect-square items-center justify-center p-6">
          <span className="text-4xl font-semibold">{index + 1}</span>
        </div>
      </McCarouselItem>
    ))}
  </McCarouselContent>
  <McCarouselPrevious />
  <McCarouselNext />
</McCarousel>

API Reference

McCarousel Props

McCarousel accepts all props from a standard div, plus:

PropTypeDefaultDescription
orientation"horizontal" | "vertical""horizontal"The direction of the carousel
optsEmblaOptionsTypeundefinedOptions passed to the Embla carousel instance
pluginsEmblaPluginsTypeundefinedPlugins passed to the Embla carousel instance
setApi(api: McCarouselApi) => voidundefinedCallback to access the Embla carousel API
classNamestringundefinedAdditional CSS classes

McCarouselContent

A container for the carousel slides. Wraps the scrollable area.

PropTypeDefaultDescription
classNamestringundefinedAdditional CSS classes

McCarouselItem

A single slide within the carousel. Each item fills the full viewport width by default.

PropTypeDefaultDescription
classNamestringundefinedAdditional CSS classes

McCarouselPrevious / McCarouselNext

Navigation buttons to scroll to the previous or next slide.

Accepts all props from Button, with the following defaults:

PropTypeDefaultDescription
variantstring"outline"Button variant
sizestring"icon-sm"Button size
classNamestringundefinedAdditional CSS classes

On this page