Carousel

import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from '@hanzo/ui'

Default

Three slides with the previous and next arrows.

Create a project

One command scaffolds the app and its config.

Connect a repository

Every push builds on the runners.

Ship

A merge to main publishes the site.

export function Default() {
return (
<YStack width="100%" minW={0} px={40}>
<Carousel width="100%" maxW={360}>
<CarouselContent>
{steps.map(([title, body]) => (
<CarouselItem key={title}>
<YStack p="$5" gap="$2" rounded="$4" borderWidth={1} borderColor="$borderColor" >
<Text fontWeight="600">{title}</Text>
<Paragraph color="$color11">{body}</Paragraph>
</YStack>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</YStack>
)
}

Loop and autoplay

Wraps at the ends and advances every four seconds until the pointer is over it.

Create a project

One command scaffolds the app and its config.

Connect a repository

Every push builds on the runners.

Ship

A merge to main publishes the site.

export function LoopAutoplay() {
return (
<YStack width="100%" minW={0} px={40}>
<Carousel width="100%" maxW={360} options={{ loop: true, autoplay: 4000 }} >
<CarouselContent>
{steps.map(([title, body]) => (
<CarouselItem key={title}>
<YStack p="$5" gap="$2" rounded="$4" bg="$color3">
<Text fontWeight="600">{title}</Text>
<Paragraph color="$color11">{body}</Paragraph>
</YStack>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</YStack>
)
}

With the api

The caller keeps the api and reads which slide settled.

Create a project
Connect a repository
Ship

Slide 1 of 3

Back to the first
export function WithApi() {
const [api, setApi] = useState<CarouselApi | null>(null)
const [index, setIndex] = useState(0)
return (
<YStack gap="$3">
<YStack width="100%" minW={0} px={40}>
<Carousel width={360} setApi={setApi} onCarouselSelect={(a) => setIndex(a.selectedScrollSnap())} >
<CarouselContent>
{steps.map(([title]) => (
<CarouselItem key={title}>
<YStack p="$5" rounded="$4" bg="$color3">
<Text fontWeight="600">{title}</Text>
</YStack>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</YStack>
<Paragraph color="$color11">
Slide {index + 1} of {steps.length}
</Paragraph>
<Text fontSize={13} color="$color12" cursor="pointer" onPress={() => api?.scrollTo(0)} >
Back to the first
</Text>
</YStack>
)
}

Types

export type CarouselOptions = {
/** Past the last slide, go to the first — and the reverse. */
loop?: boolean
/** The slide it opens on. Default 0. */
startIndex?: number
/** Where a slide settles in the viewport. Default `center`. */
align?: 'start' | 'center' | 'end'
/**
* Advance every N milliseconds.
*
* Stops while the pointer is over the carousel or focus is inside it, and
* never starts at all under `prefers-reduced-motion` — a carousel that moves
* on its own is the canonical vestibular trigger, and it also steals the
* slide out from under someone still reading it.
*/
autoplay?: number
}
export type CarouselApi = {
/**
* Centre slide `index`. Out of range clamps, or wraps when `loop`.
*
* `jump` arrives there without animating, which is what opening ON a slide
* wants — animating from the first one reads as the carousel moving by
* itself the moment it appears.
*/
scrollTo: (index: number, jump?: boolean) => void
scrollPrev: () => void
scrollNext: () => void
/** The slide nearest the centre, or -1 when there are none. */
selectedScrollSnap: () => number
canScrollPrev: () => boolean
canScrollNext: () => boolean
slideCount: () => number
/**
* Where each slide comes to rest, in scroll pixels.
*
* Its length is the number of slides, which is what a dot strip beneath a
* carousel counts — and counting the API rather than the source array is what
* keeps the dots right when the slides are conditional.
*/
scrollSnapList: () => number[]
/** Subscribe. `select` fires when the settled slide changes. */
on: (event: 'select', listener: () => void) => void
off: (event: 'select', listener: () => void) => void
}
export type CarouselProps = Omit<React.ComponentProps<typeof Box>, 'onSelect'> & {
options?: CarouselOptions
/** Handed the api once, on mount. */
setApi?: (api: CarouselApi) => void
/** Fires when the settled slide CHANGES — not on mount. */
onCarouselSelect?: (api: CarouselApi) => void
}

Exports

CarouselCarouselContentCarouselItemCarouselPreviousCarouselNexttype CarouselApitype CarouselOptionstype CarouselProps