view_carousel

Carousel

Scrollable media/content 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

Last click: (none)

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

Carousel — Props
PropTypeRequiredDefaultDescription
classNamestringNoExtra classes for the root element.
customStylesReact.CSSPropertiesNoInline styles for the root element.
ariaLabelstringNo"Carousel"Accessible label for the root <section>.
variant"multi" | "uncontained" | "multi-aspect"No"multi"Layout style. Controls whether items use size or aspect.
itemsICarouselItem[]YesData source for items.
renderItem(item: ICarouselItem, index: number) => React.ReactNodeNoCustom renderer. If omitted, item.content is used.
onItemClick(item: ICarouselItem, index: number) => voidNoIf provided (and the item is not disabled), items become interactive (click + Enter/Space).
morphbooleanNotrueEnables 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.
influencenumberNo1How “wide” the morph influence feels. Higher = effect spreads more.
snapOnReleasebooleanNotrueWhen true, the morph hook may snap items after pointer release.