Carousel
Carousel Examples
Copy-ready examples with live previews.
Basic usage
Data-driven carousel. Provide items with size + content.
Live preview
Basic (multi)
import Carousel from "@/packages/ui/src/_components/carousel/Carousel";
export default function Example() {
return (
<Carousel
ariaLabel="Featured items"
items={[
{ id: "a", size: "large", content: <div>Card A</div> },
{ id: "b", size: "medium", content: <div>Card B</div> },
{ id: "c", size: "small", content: <div>Card C</div>, disabled: true },
]}
/>
);
}Interactive items (onItemClick)
If onItemClick is provided, items become keyboard-accessible buttons (Enter/Space).
• Provide ariaLabel per item for consistent accessibility when items are interactive.
Live preview
Click + keyboard
import { useMemo, useState } from "react";
import Carousel from "@/_components/carousel/Carousel";
export default function Example() {
const [last, setLast] = useState("(none)");
const items = useMemo(
() => [
{ id: "1", size: "medium", content: "One", ariaLabel: "Item one" },
{ id: "2", size: "medium", content: "Two", ariaLabel: "Item two" },
{ id: "3", size: "medium", content: "Three", disabled: true, ariaLabel: "Item three (disabled)" },
],
[]
);
return (
<>
<div>Last click: {last}</div>
<Carousel items={items} onItemClick={(item) => setLast(item.id)} />
</>
);
}Custom rendering (renderItem)
Recommended for real UIs. If renderItem is omitted, item.content is used.
Live preview
renderItem
import Carousel from "@/packages/ui/src/_components/carousel/Carousel";
export default function Example() {
return (
<Carousel
items={[{ id: "1", size: "large" }, { id: "2", size: "large" }]}
renderItem={(item) => <div style={{ padding: 16 }}>Custom render for {item.id}</div>}
/>
);
}Variant: "multi-aspect"
Uses aspect instead of size. Morph is auto-disabled for this variant.
Live preview
Aspect items
import Carousel from "@/packages/ui/src/_components/carousel/Carousel";
export default function Example() {
return (
<Carousel
variant="multi-aspect"
items={[
{ id: "16-9", aspect: "16-9", content: <div>16:9</div> },
{ id: "1-1", aspect: "1-1", content: <div>1:1</div> },
{ id: "9-16", aspect: "9-16", content: <div>9:16</div> },
]}
/>
);
}Morph controls (playground)
Toggle morph, snapOnRelease, and influence to validate the behavior.
Live preview
Morph toggles
import { useMemo, useState } from "react";
import Carousel from "@/packages/ui/src/_components/carousel/Carousel";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";
export default function Example() {
const [morph, setMorph] = useState(true);
const [snapOnRelease, setSnapOnRelease] = useState(true);
const [influence, setInfluence] = useState(1);
const items = useMemo(
() => [
{ id: "m1", size: "large", content: <div>Large 1</div> },
{ id: "m2", size: "medium", content: <div>Medium 1</div> },
{ id: "m3", size: "small", content: <div>Small 1</div> },
{ id: "m4", size: "medium", content: <div>Medium 2</div> },
{ id: "m5", size: "large", content: <div>Large 2</div> },
{ id: "m6", size: "small", content: <div>Small 2</div> },
],
[]
);
return (
<div style={{ display: "grid", gap: 12 }}>
<div style={{ display: "flex", gap: 10, flexWrap: "wrap", alignItems: "center" }}>
<Button variant="secondary" ripple outline size="small" onClick={() => setMorph((v) => !v)}>
Morph: {morph ? "ON" : "OFF"}
</Button>
<Button variant="secondary" ripple outline size="small" onClick={() => setSnapOnRelease((v) => !v)}>
Snap: {snapOnRelease ? "ON" : "OFF"}
</Button>
<Button
variant="secondary"
ripple
outline
size="small"
onClick={() => setInfluence((v) => (v >= 2 ? 0.5 : Number((v + 0.5).toFixed(1))))}
>
Influence: {influence}
</Button>
</div>
<Carousel
items={items}
morph={morph}
snapOnRelease={snapOnRelease}
influence={influence}
morphSizes={{ large: 320, medium: 192, small: 96 }}
/>
</div>
);
}Variant: "uncontained"
Layout variant for a less boxed-in carousel feel (same size-based items).
Live preview
Uncontained
import Carousel from "@/packages/ui/src/_components/carousel/Carousel";
export default function Example() {
return (
<Carousel
variant="uncontained"
items={[
{ id: "u1", size: "medium", content: <div>A</div> },
{ id: "u2", size: "medium", content: <div>B</div> },
{ id: "u3", size: "medium", content: <div>C</div> },
{ id: "u4", size: "medium", content: <div>D</div> },
{ id: "u5", size: "medium", content: <div>E</div> },
{ id: "u6", size: "medium", content: <div>F</div> },
{ id: "u7", size: "medium", content: <div>G</div> },
{ id: "u8", size: "medium", content: <div>H</div> },
]}
/>
);
}On this page
Carousel Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| className | string | No | — | Extra classes for the root element. |
| customStyles | React.CSSProperties | No | — | Inline styles for the root element. |
| ariaLabel | string | No | "Carousel" | Accessible label for the root <section>. |
| variant | "multi" | "uncontained" | "multi-aspect" | No | "multi" | Layout style. Controls whether items use size or aspect. |
| items | ICarouselItem[] | Yes | — | Data source for items. |
| renderItem | (item: ICarouselItem, index: number) => React.ReactNode | No | — | Custom renderer. If omitted, item.content is used. |
| onItemClick | (item: ICarouselItem, index: number) => void | No | — | If provided (and the item is not disabled), items become interactive (click + Enter/Space). |
| morph | boolean | No | true | Enables morph effect while swiping/scrolling. Auto-disabled when variant="multi-aspect". |
| morphSizes | { large: number; medium: number; small: number } | No | { large: 320, medium: 192, small: 96 } | Target pixel widths used by the morphing algorithm. |
| influence | number | No | 1 | How “wide” the morph influence feels. Higher = effect spreads more. |
| snapOnRelease | boolean | No | true | When true, the morph hook may snap items after pointer release. |
Components