Carousel
A carousel with motion and swipe built using Embla.
import Card from '@dinui/react/card'import Carousel from '@dinui/react/carousel'
export default function CarouselDemo() { return ( <Carousel className="w-full max-w-xs"> <Carousel.Content> {Array.from({ length: 5 }).map((_, index) => ( <Carousel.Item key={index}> <div className="p-1"> <Card className="flex items-center justify-center aspect-square"> <span className="text-4xl font-semibold">{index + 1}</span> </Card> </div> </Carousel.Item> ))} </Carousel.Content> <Carousel.Previous /> <Carousel.Next /> </Carousel> )}About
The carousel component is built using the Embla Carousel library.
Sizes
To set the size of the items, you can use the basis utility class on the <Carousel.Item />.
import Card from '@dinui/react/card'import Carousel from '@dinui/react/carousel'
export default function CarouselSize() { return ( <Carousel opts={{ align: 'start', }} className="w-full max-w-sm" > <Carousel.Content> {Array.from({ length: 5 }).map((_, index) => ( <Carousel.Item key={index} className="md:basis-1/2 lg:basis-1/3"> <div className="p-1"> <Card className="flex items-center justify-center aspect-square"> <span className="text-3xl font-semibold">{index + 1}</span> </Card> </div> </Carousel.Item> ))} </Carousel.Content> <Carousel.Previous /> <Carousel.Next /> </Carousel> )}// 33% of the carousel width.<Carousel> <Carousel.Content> <Carousel.Item className="basis-1/3">...</Carousel.Item> <Carousel.Item className="basis-1/3">...</Carousel.Item> <Carousel.Item className="basis-1/3">...</Carousel.Item> </Carousel.Content></Carousel>// 50% on small screens and 33% on larger screens.<Carousel> <Carousel.Content> <Carousel.Item className="md:basis-1/2 lg:basis-1/3">...</Carousel.Item> <Carousel.Item className="md:basis-1/2 lg:basis-1/3">...</Carousel.Item> <Carousel.Item className="md:basis-1/2 lg:basis-1/3">...</Carousel.Item> </Carousel.Content></Carousel>Spacing
To set the spacing between the items, we use a pl-[VALUE] utility on the <Carousel.Item /> and a negative -ml-[VALUE] on the <Carousel.Content />.
import Card from '@dinui/react/card'import Carousel from '@dinui/react/carousel'
export default function CarouselSpacing() { return ( <Carousel className="w-full max-w-sm"> <Carousel.Content className="-ml-1"> {Array.from({ length: 5 }).map((_, index) => ( <Carousel.Item key={index} className="pl-1 md:basis-1/2 lg:basis-1/3"> <div className="p-1"> <Card className="flex items-center justify-center aspect-square"> <span className="text-2xl font-semibold">{index + 1}</span> </Card> </div> </Carousel.Item> ))} </Carousel.Content> <Carousel.Previous /> <Carousel.Next /> </Carousel> )}<Carousel> <Carousel.Content className="-ml-4"> <Carousel.Item className="pl-4">...</Carousel.Item> <Carousel.Item className="pl-4">...</Carousel.Item> <Carousel.Item className="pl-4">...</Carousel.Item> </Carousel.Content></Carousel><Carousel> <Carousel.Content className="-ml-2 md:-ml-4"> <Carousel.Item className="pl-2 md:pl-4">...</Carousel.Item> <Carousel.Item className="pl-2 md:pl-4">...</Carousel.Item> <Carousel.Item className="pl-2 md:pl-4">...</Carousel.Item> </Carousel.Content></Carousel>Orientation
Use the orientation prop to set the orientation of the carousel.
import Card from '@dinui/react/card'import Carousel from '@dinui/react/carousel'
export default function CarouselOrientation() { return ( <Carousel opts={{ align: 'start', }} orientation="vertical" className="w-full max-w-xs" > <Carousel.Content className="-mt-1 h-[200px]"> {Array.from({ length: 5 }).map((_, index) => ( <Carousel.Item key={index} className="pt-1 md:basis-1/2"> <div className="p-1"> <Card className="flex items-center justify-center p-6"> <span className="text-3xl font-semibold">{index + 1}</span> </Card> </div> </Carousel.Item> ))} </Carousel.Content> <Carousel.Previous /> <Carousel.Next /> </Carousel> )}<Carousel orientation="vertical | horizontal"> <Carousel.Content> <Carousel.Item>...</Carousel.Item> <Carousel.Item>...</Carousel.Item> <Carousel.Item>...</Carousel.Item> </Carousel.Content></Carousel>Options
You can pass options to the carousel using the opts prop. See the Embla Carousel docs for more information.
<Carousel opts={{ align: 'start', loop: true, }}> <Carousel.Content> <Carousel.Item>...</Carousel.Item> <Carousel.Item>...</Carousel.Item> <Carousel.Item>...</Carousel.Item> </Carousel.Content></Carousel>API
Use a state and the setApi props to get an instance of the carousel API.
import Card from '@dinui/react/card'import Carousel, { type CarouselApi } from '@dinui/react/carousel'import * as React from 'react'
export default function CarouselDApiDemo() { const [api, setApi] = React.useState<CarouselApi>() const [current, setCurrent] = React.useState(0) const [count, setCount] = React.useState(0)
React.useEffect(() => { if (!api) { return }
setCount(api.scrollSnapList().length) setCurrent(api.selectedScrollSnap() + 1)
api.on('select', () => { setCurrent(api.selectedScrollSnap() + 1) }) }, [api])
return ( <div> <Carousel setApi={setApi} className="w-full max-w-xs"> <Carousel.Content> {Array.from({ length: 5 }).map((_, index) => ( <Carousel.Item key={index}> <Card className="flex items-center justify-center aspect-square"> <span className="text-4xl font-semibold">{index + 1}</span> </Card> </Carousel.Item> ))} </Carousel.Content> <Carousel.Previous /> <Carousel.Next /> </Carousel> <div className="py-2 text-center text-sm text-fg-weaker"> Slide {current} of {count} </div> </div> )}import { type CarouselApi } from '@dinui/react/carousel'
export function Example() { const [api, setApi] = React.useState<CarouselApi>() const [current, setCurrent] = React.useState(0) const [count, setCount] = React.useState(0)
React.useEffect(() => { if (!api) { return }
setCount(api.scrollSnapList().length) setCurrent(api.selectedScrollSnap() + 1)
api.on('select', () => { setCurrent(api.selectedScrollSnap() + 1) }) }, [api])
return ( <Carousel setApi={setApi}> <Carousel.Content> <Carousel.Item>...</Carousel.Item> <Carousel.Item>...</Carousel.Item> <Carousel.Item>...</Carousel.Item> </Carousel.Content> </Carousel> )}Events
You can listen to events using the api instance from setApi.
import { type CarouselApi } from '@dinui/react/carousel'
export function Example() { const [api, setApi] = React.useState<CarouselApi>()
React.useEffect(() => { if (!api) { return }
api.on('select', () => { // Do something on select. }) }, [api])
return ( <Carousel setApi={setApi}> <Carousel.Content> <Carousel.Item>...</Carousel.Item> <Carousel.Item>...</Carousel.Item> <Carousel.Item>...</Carousel.Item> </Carousel.Content> </Carousel> )}See the Embla Carousel docs for more information on using events.
Plugins
You can use the plugins prop to add plugins to the carousel.
import Autoplay from "embla-carousel/autoplay"
export function Example() { return ( <Carousel plugins={[ Autoplay({ delay: 2000, }), ]} > // ... </Carousel> )}import Card from '@dinui/react/card'import Carousel from '@dinui/react/carousel'import Autoplay from 'embla-carousel-autoplay'import * as React from 'react'
export default function CarouselPlugin() { const plugin = React.useRef(Autoplay({ delay: 2000, stopOnInteraction: true }))
return ( <Carousel plugins={[plugin.current]} className="w-full max-w-xs" onMouseEnter={plugin.current.stop} onMouseLeave={plugin.current.reset} > <Carousel.Content> {Array.from({ length: 5 }).map((_, index) => ( <Carousel.Item key={index}> <div className="p-1"> <Card className="flex items-center justify-center aspect-square"> <span className="text-4xl font-semibold">{index + 1}</span> </Card> </div> </Carousel.Item> ))} </Carousel.Content> <Carousel.Previous /> <Carousel.Next /> </Carousel> )}See the Embla Carousel docs for more information on using plugins.
Installation
-
Follow Installation Guide
To enable DinUI functionality in your project, you will need to properly set up Tailwind and install the necessary dependencies. -
All done
You now can start using this component in your project.
-
Follow Installation Guide
To enable DinUI functionality in your project, you will need to properly set up Tailwind and install the necessary dependencies. -
Run the following command in your project
Terminal window npx @dinui/cli@latest add carouselTerminal window yarn dlx @dinui/cli@latest add carouselTerminal window pnpm dlx @dinui/cli@latest add carouselTerminal window bunx @dinui/cli@latest add carousel -
Update the import paths to match your project setup
-
All done
You now can start using this component in your project.
-
Follow Installation Guide
To enable DinUI functionality in your project, you will need to properly set up Tailwind and install the necessary dependencies. -
Install dependencies
Terminal window npm install @tabler/icons-react embla-carousel-react tailwind-variants type-fest embla-carousel-reactTerminal window yarn add @tabler/icons-react embla-carousel-react tailwind-variants type-fest embla-carousel-reactTerminal window pnpm add @tabler/icons-react embla-carousel-react tailwind-variants type-fest embla-carousel-reactTerminal window bun add @tabler/icons-react embla-carousel-react tailwind-variants type-fest embla-carousel-react -
Copy and paste the following code into your project
'use client'import Button from '@dinui/react/button'import { IconArrowLeft, IconArrowRight } from '@tabler/icons-react'import useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react'import { createContext, forwardRef, useCallback, useContext, useEffect, useState } from 'react'import { tv } from 'tailwind-variants'import type { Merge } from 'type-fest'type CarouselApi = UseEmblaCarouselType[1]type UseCarouselParameters = Parameters<typeof useEmblaCarousel>type CarouselOptions = UseCarouselParameters[0]type CarouselPlugin = UseCarouselParameters[1]type CarouselProps = {opts?: CarouselOptionsplugins?: CarouselPluginorientation?: 'horizontal' | 'vertical'setApi?: (api: CarouselApi) => void}type CarouselContextProps = {carouselRef: ReturnType<typeof useEmblaCarousel>[0]api: ReturnType<typeof useEmblaCarousel>[1]scrollPrev: () => voidscrollNext: () => voidcanScrollPrev: booleancanScrollNext: boolean} & CarouselPropsconst carousel = tv({slots: {root: 'relative',content: 'flex',contentWrapper: 'overflow-hidden',item: 'min-w-0 shrink-0 grow-0 basis-full',previous: 'absolute rounded-full',next: 'absolute rounded-full',},variants: {orientation: {horizontal: {content: '-ml-4',item: 'pl-4',previous: '-left-12 top-1/2 -translate-y-1/2',next: '-right-12 top-1/2 -translate-y-1/2',},vertical: {content: '-mt-4 flex-col',item: 'pt-4',previous: '-top-12 left-1/2 -translate-x-1/2 rotate-90',next: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',},},},defaultVariants: {orientation: 'horizontal',},})const CarouselContext = createContext<CarouselContextProps | null>(null)function useCarousel() {const context = useContext(CarouselContext)if (!context) {throw new Error('useCarousel must be used within a <Carousel />')}return context}const CarouselRoot = forwardRef<HTMLDivElement,React.HTMLAttributes<HTMLDivElement> & CarouselProps>(({ orientation = 'horizontal', opts, setApi, plugins, ...props }, ref) => {const { root } = carousel({ orientation })const [carouselRef, api] = useEmblaCarousel({...opts,axis: orientation === 'horizontal' ? 'x' : 'y',},plugins,)const [canScrollPrev, setCanScrollPrev] = useState(false)const [canScrollNext, setCanScrollNext] = useState(false)const onSelect = useCallback((api: CarouselApi) => {if (!api) {return}setCanScrollPrev(api.canScrollPrev())setCanScrollNext(api.canScrollNext())}, [])const scrollPrev = useCallback(() => {api?.scrollPrev()}, [api])const scrollNext = useCallback(() => {api?.scrollNext()}, [api])const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {if (event.key === 'ArrowLeft') {event.preventDefault()scrollPrev()} else if (event.key === 'ArrowRight') {event.preventDefault()scrollNext()}},[scrollPrev, scrollNext],)useEffect(() => {if (!api || !setApi) {return}setApi(api)}, [api, setApi])useEffect(() => {if (!api) {return}onSelect(api)api.on('reInit', onSelect)api.on('select', onSelect)return () => {api?.off('select', onSelect)}}, [api, onSelect])return (<CarouselContext.Providervalue={{carouselRef,api: api,opts,orientation: orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),scrollPrev,scrollNext,canScrollPrev,canScrollNext,}}><divrole="region"aria-roledescription="carousel"{...props}ref={ref}onKeyDownCapture={handleKeyDown}className={root({ className: props.className })}/></CarouselContext.Provider>)})CarouselRoot.displayName = 'Carousel'const CarouselContent = forwardRef<HTMLDivElement,Merge<React.HTMLAttributes<HTMLDivElement>,{wrapperProps?: React.ComponentProps<'div'>}>>(({ wrapperProps, ...props }, ref) => {const { carouselRef, orientation } = useCarousel()const { content, contentWrapper } = carousel({ orientation })return (<div{...wrapperProps}ref={carouselRef}className={contentWrapper({ className: wrapperProps?.className })}><div {...props} ref={ref} className={content({ className: props.className })} /></div>)})CarouselContent.displayName = 'CarouselContent'const CarouselItem = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>((props, ref) => {const { orientation } = useCarousel()const { item } = carousel({ orientation })return (<divrole="group"aria-roledescription="slide"{...props}ref={ref}className={item({ className: props.className })}/>)},)CarouselItem.displayName = 'CarouselItem'const CarouselPrevious = forwardRef<HTMLButtonElement,Merge<React.ComponentPropsWithoutRef<typeof Button>,{iconProps?: React.ComponentProps<typeof Button.Icon>}>>(({ iconProps, ...props }, ref) => {const { orientation, scrollPrev, canScrollPrev } = useCarousel()const { previous } = carousel({ orientation })return (<Buttonvariant={'outline'}size={'sm'}icon{...props}ref={ref}className={previous({ className: props.className })}disabled={!canScrollPrev}onClick={scrollPrev}><Button.Icon {...iconProps}><IconArrowLeft /></Button.Icon><span className="sr-only">Previous slide</span></Button>)})CarouselPrevious.displayName = 'CarouselPrevious'const CarouselNext = forwardRef<HTMLButtonElement,Merge<React.ComponentPropsWithoutRef<typeof Button>,{iconProps?: React.ComponentProps<typeof Button.Icon>}>>(({ iconProps, ...props }, ref) => {const { orientation, scrollNext, canScrollNext } = useCarousel()const { next } = carousel({ orientation })return (<Buttonvariant={'outline'}size={'sm'}icon{...props}ref={ref}className={next({ className: props.className })}disabled={!canScrollNext}onClick={scrollNext}><Button.Icon {...iconProps}><IconArrowRight /></Button.Icon><span className="sr-only">Next slide</span></Button>)})CarouselNext.displayName = 'CarouselNext'const Carousel = Object.assign(CarouselRoot, {Content: CarouselContent,Item: CarouselItem,Previous: CarouselPrevious,Next: CarouselNext,})export default Carouselexport { carousel, type CarouselApi }export * as CarouselPrimitive from 'embla-carousel-react' -
Update the import paths to match your project setup
-
All done
You now can start using this component in your project.