Carousel
An interactive image carousel component that allows users to cycle through a collection of content.
Docsimport { Index } from "solid-js";
import {
Carousel,
CarouselControl,
CarouselItemGroup,
CarouselItem,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselIndicatorGroup,
CarouselIndicator,
} from "~/components/carousel";
const images = [
{ src: "https://picsum.photos/seed/carousel-1/600/400", alt: "Mountain landscape" },
{ src: "https://picsum.photos/seed/carousel-2/600/400", alt: "Ocean view" },
{ src: "https://picsum.photos/seed/carousel-3/600/400", alt: "Forest trail" },
{ src: "https://picsum.photos/seed/carousel-4/600/400", alt: "Cityscape" },
{ src: "https://picsum.photos/seed/carousel-5/600/400", alt: "Desert dunes" },
];
export default function CarouselBasicDemo() {
return (
<div>
<Carousel slideCount={images.length} class="w-full max-w-lg mx-auto">
<CarouselControl>
<CarouselPrevTrigger class="inline-flex items-center justify-center size-9 rounded-md border border-input bg-background hover:bg-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="m15 18-6-6 6-6" />
</svg>
</CarouselPrevTrigger>
<CarouselItemGroup>
<Index each={images}>
{(image, index) => (
<CarouselItem index={index} class="flex-0 flex-shrink-0 min-w-0 w-full">
<img
src={image().src}
alt={image().alt}
class="w-full h-48 object-cover rounded-lg"
/>
</CarouselItem>
)}
</Index>
</CarouselItemGroup>
<CarouselNextTrigger class="inline-flex items-center justify-center size-9 rounded-md border border-input bg-background hover:bg-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="m9 18 6-6-6-6" />
</svg>
</CarouselNextTrigger>
</CarouselControl>
<CarouselIndicatorGroup class="flex justify-center gap-2 mt-4">
<Index each={images}>
{(_, index) => (
<CarouselIndicator
index={index}
class="size-2.5 rounded-full bg-muted data-[current]:bg-foreground cursor-pointer transition-colors"
/>
)}
</Index>
</CarouselIndicatorGroup>
</Carousel>
</div>
);
}Installation
CLI
Run the following command to add the component to your project:
npx @ark-preset/cli@latest add carouselManual
Create the recipe file at src/components/recipes/carousel.ts:
import { tv } from "tailwind-variants";
export const carouselVariants = tv({
slots: {
root: "flex flex-col relative w-full",
control: "flex items-center justify-between gap-2",
itemGroup: "flex flex-1 overflow-hidden rounded-lg",
item: "flex-0 flex-shrink-0 min-w-0",
prevTrigger:
"inline-flex items-center justify-center size-9 cursor-pointer rounded-md border border-input bg-background transition duration-300 motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0.5 active:duration-150 hover:bg-accent disabled:pointer-events-none disabled:opacity-50",
nextTrigger:
"inline-flex items-center justify-center size-9 cursor-pointer rounded-md border border-input bg-background transition duration-300 motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0.5 active:duration-150 hover:bg-accent disabled:pointer-events-none disabled:opacity-50",
indicatorGroup: "flex justify-center gap-2",
indicator:
"size-2.5 rounded-full bg-muted data-[current]:bg-foreground cursor-pointer transition-colors",
autoplayTrigger:
"inline-flex items-center justify-center size-8 rounded-full bg-background/80 transition duration-300 motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0.5 active:duration-150 hover:bg-accent",
progressText: "text-sm font-medium font-variant-numeric tabular-nums",
},
});
export type CarouselVariants = ReturnType<typeof carouselVariants>;Create the component directory and files.
src/components/carousel/carousel.base.tsx:
import { Carousel as ArkCarousel } from "@ark-ui/solid/carousel";
import { splitProps, type Component } from "solid-js";
import { carouselVariants } from "../recipes/carousel";
const styles = carouselVariants();
const Root: Component<ArkCarousel.RootProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkCarousel.Root class={styles.root({ class: local.class })} {...others} />;
};
const RootProvider: Component<ArkCarousel.RootProviderProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkCarousel.RootProvider class={styles.root({ class: local.class })} {...others} />;
};
const Control: Component<ArkCarousel.ControlProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkCarousel.Control class={styles.control({ class: local.class })} {...others} />;
};
const ItemGroup: Component<ArkCarousel.ItemGroupProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkCarousel.ItemGroup class={styles.itemGroup({ class: local.class })} {...others} />;
};
const Item: Component<ArkCarousel.ItemProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkCarousel.Item class={styles.item({ class: local.class })} {...others} />;
};
const PrevTrigger: Component<ArkCarousel.PrevTriggerProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkCarousel.PrevTrigger class={styles.prevTrigger({ class: local.class })} {...others} />;
};
const NextTrigger: Component<ArkCarousel.NextTriggerProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkCarousel.NextTrigger class={styles.nextTrigger({ class: local.class })} {...others} />;
};
const IndicatorGroup: Component<ArkCarousel.IndicatorGroupProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkCarousel.IndicatorGroup class={styles.indicatorGroup({ class: local.class })} {...others} />
);
};
const Indicator: Component<ArkCarousel.IndicatorProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkCarousel.Indicator class={styles.indicator({ class: local.class })} {...others} />;
};
const AutoplayTrigger: Component<ArkCarousel.AutoplayTriggerProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkCarousel.AutoplayTrigger
class={styles.autoplayTrigger({ class: local.class })}
{...others}
/>
);
};
const ProgressText: Component<ArkCarousel.ProgressTextProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkCarousel.ProgressText class={styles.progressText({ class: local.class })} {...others} />
);
};
const AutoplayIndicator = ArkCarousel.AutoplayIndicator;
export const Carousel = {
Root,
RootProvider,
Control,
ItemGroup,
Item,
PrevTrigger,
NextTrigger,
IndicatorGroup,
Indicator,
AutoplayTrigger,
ProgressText,
AutoplayIndicator,
};src/components/carousel/index.tsx:
import { Carousel as CarouselBase } from "./carousel.base";
const Carousel = CarouselBase.Root;
const CarouselControl = CarouselBase.Control;
const CarouselItemGroup = CarouselBase.ItemGroup;
const CarouselItem = CarouselBase.Item;
const CarouselPrevTrigger = CarouselBase.PrevTrigger;
const CarouselNextTrigger = CarouselBase.NextTrigger;
const CarouselIndicatorGroup = CarouselBase.IndicatorGroup;
const CarouselIndicator = CarouselBase.Indicator;
const CarouselAutoplayTrigger = CarouselBase.AutoplayTrigger;
const CarouselProgressText = CarouselBase.ProgressText;
export {
Carousel,
CarouselControl,
CarouselItemGroup,
CarouselItem,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselIndicatorGroup,
CarouselIndicator,
CarouselAutoplayTrigger,
CarouselProgressText,
CarouselBase,
};
export { carouselVariants, type CarouselVariants } from "../recipes/carousel";Note: Make sure your project has the Tailwind CSS theme variables set up (
--background,--foreground,--ring,--border, etc.) or override the utility classes to match your design system.
Usage
Basic Usage
import { Index } from "solid-js";
import {
Carousel,
CarouselControl,
CarouselItemGroup,
CarouselItem,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselIndicatorGroup,
CarouselIndicator,
} from "~/components/carousel";
const images = [
{ src: "https://picsum.photos/seed/carousel-1/600/400", alt: "Mountain landscape" },
{ src: "https://picsum.photos/seed/carousel-2/600/400", alt: "Ocean view" },
{ src: "https://picsum.photos/seed/carousel-3/600/400", alt: "Forest trail" },
{ src: "https://picsum.photos/seed/carousel-4/600/400", alt: "Cityscape" },
{ src: "https://picsum.photos/seed/carousel-5/600/400", alt: "Desert dunes" },
];
export default function CarouselBasicDemo() {
return (
<div>
<Carousel slideCount={images.length} class="w-full max-w-lg mx-auto">
<CarouselControl>
<CarouselPrevTrigger class="inline-flex items-center justify-center size-9 rounded-md border border-input bg-background hover:bg-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="m15 18-6-6 6-6" />
</svg>
</CarouselPrevTrigger>
<CarouselItemGroup>
<Index each={images}>
{(image, index) => (
<CarouselItem index={index} class="flex-0 flex-shrink-0 min-w-0 w-full">
<img
src={image().src}
alt={image().alt}
class="w-full h-48 object-cover rounded-lg"
/>
</CarouselItem>
)}
</Index>
</CarouselItemGroup>
<CarouselNextTrigger class="inline-flex items-center justify-center size-9 rounded-md border border-input bg-background hover:bg-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="m9 18 6-6-6-6" />
</svg>
</CarouselNextTrigger>
</CarouselControl>
<CarouselIndicatorGroup class="flex justify-center gap-2 mt-4">
<Index each={images}>
{(_, index) => (
<CarouselIndicator
index={index}
class="size-2.5 rounded-full bg-muted data-[current]:bg-foreground cursor-pointer transition-colors"
/>
)}
</Index>
</CarouselIndicatorGroup>
</Carousel>
</div>
);
}With Loop
Enable looping to allow the carousel to wrap around:
import { Index } from "solid-js";
import {
Carousel,
CarouselControl,
CarouselItemGroup,
CarouselItem,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselIndicatorGroup,
CarouselIndicator,
} from "~/components/carousel";
const images = [
{ src: "https://picsum.photos/seed/carousel-1/600/400", alt: "Mountain landscape" },
{ src: "https://picsum.photos/seed/carousel-2/600/400", alt: "Ocean view" },
{ src: "https://picsum.photos/seed/carousel-3/600/400", alt: "Forest trail" },
{ src: "https://picsum.photos/seed/carousel-4/600/400", alt: "Cityscape" },
{ src: "https://picsum.photos/seed/carousel-5/600/400", alt: "Desert dunes" },
];
export default function CarouselLoopDemo() {
return (
<div>
<Carousel slideCount={images.length} loop class="w-full max-w-lg mx-auto">
<CarouselControl>
<CarouselPrevTrigger class="inline-flex items-center justify-center size-9 rounded-md border border-input bg-background hover:bg-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="m15 18-6-6 6-6" />
</svg>
</CarouselPrevTrigger>
<CarouselItemGroup>
<Index each={images}>
{(image, index) => (
<CarouselItem index={index} class="flex-0 flex-shrink-0 min-w-0 w-full">
<img
src={image().src}
alt={image().alt}
class="w-full h-48 object-cover rounded-lg"
/>
</CarouselItem>
)}
</Index>
</CarouselItemGroup>
<CarouselNextTrigger class="inline-flex items-center justify-center size-9 rounded-md border border-input bg-background hover:bg-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="m9 18 6-6-6-6" />
</svg>
</CarouselNextTrigger>
</CarouselControl>
<CarouselIndicatorGroup class="flex justify-center gap-2 mt-4">
<Index each={images}>
{(_, index) => (
<CarouselIndicator
index={index}
class="size-2.5 rounded-full bg-muted data-[current]:bg-foreground cursor-pointer transition-colors"
/>
)}
</Index>
</CarouselIndicatorGroup>
</Carousel>
</div>
);
}With Autoplay
Enable automatic scrolling:
import { Index } from "solid-js";
import {
Carousel,
CarouselControl,
CarouselItemGroup,
CarouselItem,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselIndicatorGroup,
CarouselIndicator,
} from "~/components/carousel";
const images = [
{ src: "https://picsum.photos/seed/carousel-1/600/400", alt: "Mountain landscape" },
{ src: "https://picsum.photos/seed/carousel-2/600/400", alt: "Ocean view" },
{ src: "https://picsum.photos/seed/carousel-3/600/400", alt: "Forest trail" },
{ src: "https://picsum.photos/seed/carousel-4/600/400", alt: "Cityscape" },
{ src: "https://picsum.photos/seed/carousel-5/600/400", alt: "Desert dunes" },
];
export default function CarouselAutoplayDemo() {
return (
<div>
<Carousel slideCount={images.length} autoplay class="w-full max-w-lg mx-auto">
<CarouselControl>
<CarouselPrevTrigger class="inline-flex items-center justify-center size-9 rounded-md border border-input bg-background hover:bg-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="m15 18-6-6 6-6" />
</svg>
</CarouselPrevTrigger>
<CarouselItemGroup>
<Index each={images}>
{(image, index) => (
<CarouselItem index={index} class="flex-0 flex-shrink-0 min-w-0 w-full">
<img
src={image().src}
alt={image().alt}
class="w-full h-48 object-cover rounded-lg"
/>
</CarouselItem>
)}
</Index>
</CarouselItemGroup>
<CarouselNextTrigger class="inline-flex items-center justify-center size-9 rounded-md border border-input bg-background hover:bg-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="m9 18 6-6-6-6" />
</svg>
</CarouselNextTrigger>
</CarouselControl>
<CarouselIndicatorGroup class="flex justify-center gap-2 mt-4">
<Index each={images}>
{(_, index) => (
<CarouselIndicator
index={index}
class="size-2.5 rounded-full bg-muted data-[current]:bg-foreground cursor-pointer transition-colors"
/>
)}
</Index>
</CarouselIndicatorGroup>
</Carousel>
</div>
);
}API Reference
See the Ark UI Carousel documentation.