Components
Carousel

Carousel

A slideshow component that cycles through elements.

Usage

import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import * as Carousel from '~/components/ui/carousel'
import { IconButton } from '~/components/ui/icon-button'

export const Demo = (props: Carousel.RootProps) => {
  const images = [
    'https://tinyurl.com/5b6ka8jd',
    'https://tinyurl.com/7rmccdn5',
    'https://tinyurl.com/59jxz9uu',
    'https://tinyurl.com/6jurv23t',
    'https://tinyurl.com/yp4rfum7',
  ]
  return (
    <Carousel.Root {...props}>
      <Carousel.Viewport>
        <Carousel.ItemGroup>
          {images.map((image, index) => (
            <Carousel.Item key={index} index={index}>
              <img
                src={image}
                alt={`Slide ${index}`}
                style={{ height: '398px', width: '100%', objectFit: 'cover' }}
              />
            </Carousel.Item>
          ))}
        </Carousel.ItemGroup>
        <Carousel.Control>
          <Carousel.PrevTrigger asChild>
            <IconButton size="sm" variant="link" aria-label="Previous Slide">
              <ChevronLeftIcon />
            </IconButton>
          </Carousel.PrevTrigger>
          <Carousel.IndicatorGroup>
            {images.map((_, index) => (
              <Carousel.Indicator
                key={index}
                index={index}
                aria-label={`Goto slide ${index + 1}`}
              />
            ))}
          </Carousel.IndicatorGroup>
          <Carousel.NextTrigger asChild>
            <IconButton size="sm" variant="link" aria-label="Next Slide">
              <ChevronRightIcon />
            </IconButton>
          </Carousel.NextTrigger>
        </Carousel.Control>
      </Carousel.Viewport>
    </Carousel.Root>
  )
}

Installation

1

Add Component

Insert code snippet into your project. Update import paths as needed.

import { Carousel } from '@ark-ui/react/carousel'
import type { ComponentProps } from 'react'
import { styled } from 'styled-system/jsx'
import { carousel } from 'styled-system/recipes'
import { createStyleContext } from '~/lib/create-style-context'

const { withProvider, withContext } = createStyleContext(carousel)

export const Root = withProvider(styled(Carousel.Root), 'root')
export const Control = withContext(styled(Carousel.Control), 'control')
export const Indicator = withContext(styled(Carousel.Indicator), 'indicator')
export const IndicatorGroup = withContext(styled(Carousel.IndicatorGroup), 'indicatorGroup')
export const Item = withContext(styled(Carousel.Item), 'item')
export const ItemGroup = withContext(styled(Carousel.ItemGroup), 'itemGroup')
export const NextTrigger = withContext(styled(Carousel.NextTrigger), 'nextTrigger')
export const PrevTrigger = withContext(styled(Carousel.PrevTrigger), 'prevTrigger')
export const Viewport = withContext(styled(Carousel.Viewport), 'viewport')

export interface RootProps extends ComponentProps<typeof Root> {}
export interface ControlProps extends ComponentProps<typeof Control> {}
export interface IndicatorProps extends ComponentProps<typeof Indicator> {}
export interface IndicatorGroupProps extends ComponentProps<typeof IndicatorGroup> {}
export interface ItemProps extends ComponentProps<typeof Item> {}
export interface ItemGroupProps extends ComponentProps<typeof ItemGroup> {}
export interface NextTriggerProps extends ComponentProps<typeof NextTrigger> {}
export interface PrevTriggerProps extends ComponentProps<typeof PrevTrigger> {}
export interface ViewportProps extends ComponentProps<typeof Viewport> {}
2

Add Recipe

This step is necessary only if you do not use any of the Park UI plugins.

import { carouselAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'

export const carousel = defineSlotRecipe({
  className: 'carousel',
  slots: carouselAnatomy.keys(),
  base: {
    root: {
      colorPalette: 'accent',
    },
    viewport: {
      overflowX: 'hidden',
      position: 'relative',
      borderRadius: 'l2',
    },
    control: {
      alignItems: 'center',
      background: { base: 'gray.dark.a12', _dark: 'gray.light.a12' },
      borderRadius: 'l2',
      bottom: '4',
      display: 'flex',
      left: '50%',
      position: 'absolute',
      transform: 'translateX(-50%)',
    },
    indicatorGroup: {
      display: 'flex',
    },
    indicator: {
      borderRadius: 'full',
      background: 'gray.6',
      cursor: 'pointer',
      _current: {
        background: 'colorPalette.default',
      },
      _focusVisible: {
        outlineOffset: '2px',
        outline: '2px solid',
        outlineColor: 'border.outline',
      },
    },
  },
  defaultVariants: {
    size: 'md',
  },
  variants: {
    size: {
      sm: {
        control: {
          gap: '1',
          p: '1',
        },
        indicatorGroup: {
          gap: '2',
        },
        indicator: {
          width: '2',
          height: '2',
        },
      },
      md: {
        control: {
          gap: '2',
          p: '2.5',
        },
        indicatorGroup: {
          gap: '3',
        },
        indicator: {
          width: '2.5',
          height: '2.5',
        },
      },
    },
  },
})

Previous

Card

On this page