1import { For } from "solid-js";2
3import { Card, CardContent } from "~/components/ui/card";4import {5 Carousel,6 CarouselContent,7 CarouselItem,8 CarouselNext,9 CarouselPrevious,10} from "~/components/ui/carousel";11
12export default function CarouselDemo() {13 return (14 <Carousel class="w-full max-w-xs">15 <CarouselContent>16 <For each={[1, 2, 3, 4, 5]}>17 {(_, index) => (18 <CarouselItem>19 <div class="p-1">20 <Card>21 <CardContent class="flex aspect-square items-center justify-center p-6">22 <span class="text-4xl font-semibold">{index() + 1}</span>23 </CardContent>24 </Card>25 </div>26 </CarouselItem>27 )}28 </For>29 </CarouselContent>30 <CarouselPrevious />31 <CarouselNext />32 </Carousel>33 );34}
npx shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/carousel.json
yarn shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/carousel.json
pnpm dlx shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/carousel.json
bunx --bun shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/carousel.json
Install the following dependencies
npm install embla-carousel-solid
yarn add embla-carousel-solid
pnpm add embla-carousel-solid
bun add embla-carousel-solid
Install the following component dependencies
Copy and paste the following code into your project
1import type { ButtonProps } from "~/components/ui/button";2import type { CreateEmblaCarouselType } from "embla-carousel-solid";3import type { Accessor, Component, ComponentProps, VoidProps } from "solid-js";4
5import {6 createContext,7 createEffect,8 createMemo,9 createSignal,10 mergeProps,11 splitProps,12 useContext,13} from "solid-js";14import createEmblaCarousel from "embla-carousel-solid";15import ArrowLeftIcon from "lucide-solid/icons/arrow-left";16import ArrowRightIcon from "lucide-solid/icons/arrow-right";17
18import { Button } from "~/components/ui/button";19import { cn } from "~/lib/utils";20
21export type CarouselApi = CreateEmblaCarouselType[1];22
23type UseCarouselParameters = Parameters<typeof createEmblaCarousel>;24type CarouselOptions = NonNullable<UseCarouselParameters[0]>;25type CarouselPlugin = NonNullable<UseCarouselParameters[1]>;26
27type CarouselProps = {28 opts?: ReturnType<CarouselOptions>;29 plugins?: ReturnType<CarouselPlugin>;30 orientation?: "horizontal" | "vertical";31 setApi?: (api: CarouselApi) => void;32};33
34type CarouselContextProps = {35 carouselRef: ReturnType<typeof createEmblaCarousel>[0];36 api: ReturnType<typeof createEmblaCarousel>[1];37 scrollPrev: () => void;38 scrollNext: () => void;39 canScrollPrev: Accessor<boolean>;40 canScrollNext: Accessor<boolean>;41} & CarouselProps;42
43const CarouselContext = createContext<Accessor<CarouselContextProps> | null>(44 null,45);46
47const useCarousel = () => {48 const context = useContext(CarouselContext);49
50 if (!context) {51 throw new Error("useCarousel must be used within a <Carousel />");52 }53
54 return context();55};56
57const Carousel: Component<CarouselProps & ComponentProps<"div">> = (58 rawProps,59) => {60 const props = mergeProps<(CarouselProps & ComponentProps<"div">)[]>(61 { orientation: "horizontal" },62 rawProps,63 );64
65 const [local, others] = splitProps(props, [66 "orientation",67 "opts",68 "setApi",69 "plugins",70 "class",71 "children",72 ]);73
74 const [carouselRef, api] = createEmblaCarousel(75 () => ({76 ...local.opts,77 axis: local.orientation === "horizontal" ? "x" : "y",78 }),79 () => (local.plugins === undefined ? [] : local.plugins),80 );81 const [canScrollPrev, setCanScrollPrev] = createSignal(false);82 const [canScrollNext, setCanScrollNext] = createSignal(false);83
84 const onSelect = (api: NonNullable<ReturnType<CarouselApi>>) => {85 setCanScrollPrev(api.canScrollPrev());86 setCanScrollNext(api.canScrollNext());87 };88
89 const scrollPrev = () => {90 api()?.scrollPrev();91 };92
93 const scrollNext = () => {94 api()?.scrollNext();95 };96
97 const handleKeyDown = (event: KeyboardEvent) => {98 if (event.key === "ArrowLeft") {99 event.preventDefault();100 scrollPrev();101 } else if (event.key === "ArrowRight") {102 event.preventDefault();103 scrollNext();104 }105 };106
107 createEffect(() => {108 if (!api() || !local.setApi) {109 return;110 }111 local.setApi(api);112 });113
114 createEffect(() => {115 if (!api()) {116 return;117 }118
119 onSelect(api()!);120 api()!.on("reInit", onSelect);121 api()!.on("select", onSelect);122
123 return () => {124 api()?.off("select", onSelect);125 };126 });127
128 const value = createMemo(129 () =>130 ({131 carouselRef,132 api,133 opts: local.opts,134 orientation:135 local.orientation ||136 (local.opts?.axis === "y" ? "vertical" : "horizontal"),137 scrollPrev,138 scrollNext,139 canScrollPrev,140 canScrollNext,141 }) satisfies CarouselContextProps,142 );143
144 return (145 <CarouselContext.Provider value={value}>146 <div147 data-slot="carousel"148 onKeyDown={handleKeyDown}149 class={cn("relative", local.class)}150 role="region"151 aria-roledescription="carousel"152 {...others}153 >154 {local.children}155 </div>156 </CarouselContext.Provider>157 );158};159
160const CarouselContent: Component<ComponentProps<"div">> = (props) => {161 const [local, others] = splitProps(props, ["class"]);162 const { carouselRef, orientation } = useCarousel();163
164 return (165 <div data-slot="carousel-content" ref={carouselRef} class="overflow-hidden">166 <div167 class={cn(168 "flex",169 orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",170 local.class,171 )}172 {...others}173 />174 </div>175 );176};177
178const CarouselItem: Component<ComponentProps<"div">> = (props) => {179 const [local, others] = splitProps(props, ["class"]);180 const { orientation } = useCarousel();181
182 return (183 <div184 data-slot="carousel-item"185 role="group"186 aria-roledescription="slide"187 class={cn(188 "min-w-0 shrink-0 grow-0 basis-full",189 orientation === "horizontal" ? "pl-4" : "pt-4",190 local.class,191 )}192 {...others}193 />194 );195};196
197type CarouselButtonProps = VoidProps<ButtonProps>;198
199const CarouselPrevious: Component<CarouselButtonProps> = (rawProps) => {200 const props = mergeProps<CarouselButtonProps[]>(201 { variant: "no-shadow", size: "icon" },202 rawProps,203 );204 const [local, others] = splitProps(props, ["class", "variant", "size"]);205 const { orientation, scrollPrev, canScrollPrev } = useCarousel();206
207 return (208 <Button209 data-slot="carousel-previous"210 variant={local.variant}211 size={local.size}212 class={cn(213 "absolute size-8 touch-manipulation rounded-full",214 orientation === "horizontal"215 ? "top-1/2 -left-12 -translate-y-1/2"216 : "-top-12 left-1/2 -translate-x-1/2 rotate-90",217 local.class,218 )}219 disabled={!canScrollPrev()}220 onClick={scrollPrev}221 {...others}222 >223 <ArrowLeftIcon class="size-4" />224 <span class="sr-only">Previous slide</span>225 </Button>226 );227};228
229const CarouselNext: Component<CarouselButtonProps> = (rawProps) => {230 const props = mergeProps<CarouselButtonProps[]>(231 { variant: "no-shadow", size: "icon" },232 rawProps,233 );234 const [local, others] = splitProps(props, ["class", "variant", "size"]);235 const { orientation, scrollNext, canScrollNext } = useCarousel();236
237 return (238 <Button239 data-slot="carousel-next"240 variant={local.variant}241 size={local.size}242 class={cn(243 "absolute size-8 touch-manipulation rounded-full",244 orientation === "horizontal"245 ? "top-1/2 -right-12 -translate-y-1/2"246 : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",247 local.class,248 )}249 disabled={!canScrollNext()}250 onClick={scrollNext}251 {...others}252 >253 <ArrowRightIcon class="size-4" />254 <span class="sr-only">Next slide</span>255 </Button>256 );257};258
259export {260 Carousel,261 CarouselContent,262 CarouselItem,263 CarouselPrevious,264 CarouselNext,265};
Update the import paths to match your project setup.
1import {2 Carousel,3 CarouselContent,4 CarouselItem,5 CarouselNext,6 CarouselPrevious,7} from "~/components/ui/carousel";
1<Carousel>2 <CarouselContent>3 <CarouselItem>...</CarouselItem>4 <CarouselItem>...</CarouselItem>5 <CarouselItem>...</CarouselItem>6 </CarouselContent>7 <CarouselPrevious />8 <CarouselNext />9</CarouselContent>
To set the size of the items, you can use the basis
utility class on the CarouselItem
component.
1import { For } from "solid-js";2
3import { Card, CardContent } from "~/components/ui/card";4import {5 Carousel,6 CarouselContent,7 CarouselItem,8 CarouselNext,9 CarouselPrevious,10} from "~/components/ui/carousel";11
12export default function CarouselDemo() {13 return (14 <Carousel15 class="w-full max-w-xs"16 opts={{17 align: "start",18 }}19 >20 <CarouselContent>21 <For each={[1, 2, 3, 4, 5]}>22 {(_, index) => (23 <CarouselItem class="md:basis-1/2 lg:basis-1/3">24 <div class="p-1">25 <Card>26 <CardContent class="flex aspect-square items-center justify-center p-6">27 <span class="text-4xl font-semibold">{index() + 1}</span>28 </CardContent>29 </Card>30 </div>31 </CarouselItem>32 )}33 </For>34 </CarouselContent>35 <CarouselPrevious />36 <CarouselNext />37 </Carousel>38 );39}
1// 33% of the carousel width.2<Carousel>3 <CarouselContent class="-ml-4">4 <CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>5 <CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>6 <CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>7 </CarouselContent>8</Carousel>
1// 50% on small screens and 33% on larger screens.2<Carousel>3 <CarouselContent>4 <CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>5 <CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>6 <CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>7 </CarouselContent>8</Carousel>
To change the orientation of the carousel, you can use the flex-col
utility class on the CarouselContent
component.
1import { For } from "solid-js";2
3import { Card, CardContent } from "~/components/ui/card";4import {5 Carousel,6 CarouselContent,7 CarouselItem,8 CarouselNext,9 CarouselPrevious,10} from "~/components/ui/carousel";11
12export default function CarouselDemo() {13 return (14 <Carousel15 class="w-full max-w-xs"16 opts={{17 align: "start",18 }}19 orientation="vertical"20 >21 <CarouselContent class="-mt-1 h-[200px]">22 <For each={[1, 2, 3, 4, 5]}>23 {(_, index) => (24 <CarouselItem class="pt-1 md:basis-1/2">25 <div class="p-1">26 <Card>27 <CardContent class="flex items-center justify-center p-6">28 <span class="text-4xl font-semibold">{index() + 1}</span>29 </CardContent>30 </Card>31 </div>32 </CarouselItem>33 )}34 </For>35 </CarouselContent>36 <CarouselPrevious />37 <CarouselNext />38 </Carousel>39 );40}
1<Carousel orientation="vertical | horizontal">2 <CarouselContent>3 <CarouselItem>...</CarouselItem>4 <CarouselItem>...</CarouselItem>5 <CarouselItem>...</CarouselItem>6 </CarouselContent>7</Carousel>