Carousel
A slideshow component that cycles through elements.
Usage
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Carousel, IconButton } from '~/components/ui'
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
npx @park-ui/cli components add carousel
1
Styled Primitive
Copy the code snippet below into ~/components/ui/primitives/carousel.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { Carousel } from '@ark-ui/react/carousel'
import { type CarouselVariantProps, carousel } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from '~/lib/create-style-context'
const { withProvider, withContext } = createStyleContext(carousel)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, Carousel.RootProviderBaseProps>, CarouselVariantProps>
>(Carousel.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, Carousel.RootBaseProps>, CarouselVariantProps>
>(Carousel.Root, 'root')
export const Control = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Carousel.ControlBaseProps>
>(Carousel.Control, 'control')
export const IndicatorGroup = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Carousel.IndicatorGroupBaseProps>
>(Carousel.IndicatorGroup, 'indicatorGroup')
export const Indicator = withContext<
HTMLButtonElement,
Assign<HTMLStyledProps<'button'>, Carousel.IndicatorBaseProps>
>(Carousel.Indicator, 'indicator')
export const ItemGroup = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Carousel.ItemGroupBaseProps>
>(Carousel.ItemGroup, 'itemGroup')
export const Item = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Carousel.ItemBaseProps>
>(Carousel.Item, 'item')
export const NextTrigger = withContext<
HTMLButtonElement,
Assign<HTMLStyledProps<'button'>, Carousel.NextTriggerBaseProps>
>(Carousel.NextTrigger, 'nextTrigger')
export const PrevTrigger = withContext<
HTMLButtonElement,
Assign<HTMLStyledProps<'button'>, Carousel.PrevTriggerBaseProps>
>(Carousel.PrevTrigger, 'prevTrigger')
export const Viewport = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Carousel.ViewportBaseProps>
>(Carousel.Viewport, 'viewport')
export { CarouselContext as Context } from '@ark-ui/react/carousel'
import { type Assign, Carousel } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type CarouselVariantProps, carousel } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from '~/lib/create-style-context'
const { withProvider, withContext } = createStyleContext(carousel)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, Carousel.RootProviderBaseProps>, CarouselVariantProps>
>(Carousel.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, Carousel.RootBaseProps>, CarouselVariantProps>
>(Carousel.Root, 'root')
export const Control = withContext<Assign<HTMLStyledProps<'div'>, Carousel.ControlBaseProps>>(
Carousel.Control,
'control',
)
export const IndicatorGroup = withContext<
Assign<HTMLStyledProps<'div'>, Carousel.IndicatorGroupBaseProps>
>(Carousel.IndicatorGroup, 'indicatorGroup')
export const Indicator = withContext<
Assign<HTMLStyledProps<'button'>, Carousel.IndicatorBaseProps>
>(Carousel.Indicator, 'indicator')
export const ItemGroup = withContext<Assign<HTMLStyledProps<'div'>, Carousel.ItemGroupBaseProps>>(
Carousel.ItemGroup,
'itemGroup',
)
export const Item = withContext<Assign<HTMLStyledProps<'div'>, Carousel.ItemBaseProps>>(
Carousel.Item,
'item',
)
export const NextTrigger = withContext<
Assign<HTMLStyledProps<'button'>, Carousel.NextTriggerBaseProps>
>(Carousel.NextTrigger, 'nextTrigger')
export const PrevTrigger = withContext<
Assign<HTMLStyledProps<'button'>, Carousel.PrevTriggerBaseProps>
>(Carousel.PrevTrigger, 'prevTrigger')
export const Viewport = withContext<Assign<HTMLStyledProps<'div'>, Carousel.ViewportBaseProps>>(
Carousel.Viewport,
'viewport',
)
export { CarouselContext as Context } from '@ark-ui/solid'
No snippet found
Extend ~/components/ui/primitives/index.ts
with the following line:
export * as Carousel from './carousel'
2
Integrate Recipe
If you're not using @park-ui/preset
, add the following recipe to yourpanda.config.ts
:
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: { _light: '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',
},
},
},
},
})