MiHCM
layout/@mihcm/ui·v0.21.0·stable

Carousel

Composable carousel powered by Embla. Supports horizontal/vertical orientation, loop, and prev/next controls.

Default

Horizontal carousel with previous/next controls and 5 slides.

Slide 1
Slide 2
Slide 3
Slide 4
Slide 5
<Carousel className="w-full max-w-sm">
  <CarouselContent>
    <CarouselItem>
      <div>Slide 1</div>
    </CarouselItem>

  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

Vertical orientation

Set orientation="vertical" and constrain the height to scroll vertically.

Slide 1
Slide 2
Slide 3
Slide 4
Slide 5
<Carousel orientation="vertical">
  <CarouselContent className="h-[200px]">
    <CarouselItem>…</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

Multiple slides per view

Use basis-1/3 on each CarouselItem to show three items at once.

1
2
3
4
5
6
7
8
9
<Carousel>
  <CarouselContent>
    <CarouselItem className="basis-1/3">…</CarouselItem>
    <CarouselItem className="basis-1/3">…</CarouselItem>

  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

Product showcase with pagination

Use the Carousel API from useCarousel() to build pagination dots, active-slide state, and custom controls. This mirrors the step-by-step configurator pattern: related content, a visible heading, multiple visible slides, rewind, and optional reveal spacing.

Payroll

Payroll readiness

98%

Validate approvals, exceptions, and country rules before cutoff.

Recruiting

Hiring pipeline

42

Track open roles, interview load, and pending scorecards by team.

Insights

People analytics

12k

Compare retention, engagement, and manager actions in one view.

Growth

Learning progress

86%

Surface mandatory learning, completions, and overdue courses.

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselPrevious,
  CarouselNext,
  useCarousel,
} from '@mihcm/ui/Carousel';
 
<Carousel opts={{ align: 'start', loop: true }} aria-label="Related workflow carousel">
  <CarouselContent>
    {workflowSlides.map((slide) => (
      <CarouselItem key={slide.title} className="sm:basis-1/2 lg:basis-1/3">
        <WorkflowCard {...slide} />
      </CarouselItem>
    ))}
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
  <CarouselPagination />
</Carousel>
function CarouselPagination() {
  const { api } = useCarousel();
  const [selectedIndex, setSelectedIndex] = useState(0);
  const [snapCount, setSnapCount] = useState(0);
 
  useEffect(() => {
    if (!api) return;
    const update = () => {
      setSelectedIndex(api.selectedScrollSnap());
      setSnapCount(api.scrollSnapList().length);
    };
    update();
    api.on('select', update);
    api.on('reInit', update);
    return () => {
      api.off('select', update);
      api.off('reInit', update);
    };
  }, [api]);
 
  return (
    <div className="mt-5 flex items-center justify-center gap-2">
      {Array.from({ length: snapCount }, (_, index) => (
        <button
          key={index}
          type="button"
          aria-label={`Go to slide ${index + 1}`}
          aria-current={index === selectedIndex ? 'true' : undefined}
          onClick={() => api?.scrollTo(index)}
          className={cn(
            'h-2.5 rounded-full transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
            index === selectedIndex ? 'w-6 bg-primary' : 'w-2.5 bg-muted-foreground/40',
          )}
        />
      ))}
    </div>
  );
}