# Carousel

A carousel is a dynamic user interface component that allows users to cycle through content both linearly and cyclically.

## Best practices

### Do

- Include clear, visible arrows and pagination indicators.
- Support swipe gestures on mobile for intuitive control.
- Ensure keyboard navigation, focus management, and ARIA roles.
- Optimize the layout for multiple breakpoints, including mobile, tablet, and desktop.

### Don't

- Cycle slides faster than users can read them.
- Hide navigation controls or require hover to see them.
- Use carousels where simpler layout structures would work better.

## Behavior and states

- If there are clickable actions within the content area, the user may opt into interacting with it.
- If the carousel is on auto-play, the user must always have the option of pausing the auto-play feature.
- The carousel supports two navigation options: steps and image preview.
- Setting the navigation type to image gallery replaces the steps with image thumbnails.
- The carousel mechanism collects content in one component, providing an interactive and engaging way for users to interact with various pieces of content without leaving the current page.
- This design pattern is commonly used in web and mobile applications to highlight featured articles, products, or services.
- It can also be used to create a visual narrative by sequentially displaying related content, enhancing the user experience through a seamless and intuitive interface.

## Default

Source: CoreAI.

Displays full-width image banners with overlaid content and carousel navigation controls.

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--default.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { Carousel } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import { Image } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { CarouselNavContainer } from "@coreai-ds/react-components";
import { CarouselNav } from "@coreai-ds/react-components";
import { CarouselNavButton } from "@coreai-ds/react-components";
export const Default = () => {
    const images = [
        'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
        'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
        'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg',
        'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
        'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
        'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg',
    ];
    const getAnnouncement: CarouselAnnouncerFunction = (index, totalSlides) => `Carousel slide ${index + 1} of ${totalSlides}`;
    const styles = /* css */ `
      .carousel-default-example__card {
        align-content: center;
        height: 450px;
        text-align: left;
        position: relative;
      }

      .carousel-default-example__content {
        display: flex;
        flex-direction: column;
        gap: var(--spacingHorizontalS);
        position: absolute;
        left: 10%;
        top: 25%;
        background: var(--colorNeutralBackground1);
        padding: 18px;
        max-width: 270px;
        width: 50%;
      }

      .carousel-default-example__title {
        font-family: var(--fontFamilyBase);
        font-size: var(--fontSizeBase600);
        font-weight: var(--fontWeightSemibold);
        line-height: var(--lineHeightBase600);
      }

      .carousel-default-example__subtext {
        font-family: var(--fontFamilyBase);
        font-size: var(--fontSizeBase300);
        font-weight: var(--fontWeightRegular);
        line-height: var(--lineHeightBase300);
      }
    `;
    return (<>
        <style>{styles}</style>
        <Carousel groupSize={1} circular announcement={getAnnouncement}>
          <CarouselViewport>
            <CarouselSlider>
              {images.map((imageSrc, index) => (<CarouselCard key={`image-${index}`} className="carousel-default-example__card" aria-label={`${index + 1} of ${images.length}`} id={`components-carousel-carousel--default-test-${index}`}>
                  <Image fit="cover" src={imageSrc} role="presentation"/>

                  <div className="carousel-default-example__content">
                    <div className="carousel-default-example__title">Card {index + 1}</div>
                    <div className="carousel-default-example__subtext">
                      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
                      incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
                    </div>
                    <div>
                      <Button size="small" shape="square" appearance="primary">
                        Call to action
                      </Button>
                    </div>
                  </div>
                </CarouselCard>))}
            </CarouselSlider>
          </CarouselViewport>
          <CarouselNavContainer layout="inline" autoplayTooltip={{ content: 'Autoplay', relationship: 'label' }} nextTooltip={{ content: 'Go to next', relationship: 'label' }} prevTooltip={{ content: 'Go to prev', relationship: 'label' }}>
            <CarouselNav>
              {(index) => <CarouselNavButton aria-label={`Carousel Nav Button ${index}`}/>}
            </CarouselNav>
          </CarouselNavContainer>
        </Carousel>
      </>);
};

export default Default;
```

## API

Generated CoreAI prop summaries, not replacement type declarations. Most native DOM/event and ARIA props are omitted. `?` means optional; JSDoc shows defaults, deprecations, and conditional requirements. Defaults assume the standard CoreAI provider.

### Carousel API

Carousel is the context wrapper and container for all carousel content/controls,
it has no direct style or slot opinions.

Carousel also provides API interfaces for callbacks that will occur on navigation events.

```ts
interface CarouselProps {
  /**
   * The value of the currently active page.
   */
  activeIndex?: number;

  /**
   * The alignment of the carousel.
   */
  align?: "center" | "start" | "end";

  /**
   * Localizes the string used to announce carousel page changes
   * Defaults to: undefined
   */
  announcement?: CarouselAnnouncerFunction;

  /**
   * Sets visual treatment for the Carousel container.
   *
   * `flat`
   * Retains the minimal styling used by default prior to introducing appearance-based styles.
   *
   * `elevated`
   * Applies rounded corners, background, and shadow tokens so the Carousel is presented as a surfaced container.
   * @default 'flat'
   */
  appearance?: "flat" | "elevated";

  as?: "div";

  /**
   * Choose a delay between autoplay transitions in milliseconds.
   * Only active if Autoplay is enabled via CarouselAutoplayButton
   *
   * Defaults: 4000
   */
  autoplayInterval?: number;

  children?: any;

  /**
   * Circular enables the carousel to loop back around on navigation past trailing index.
   */
  circular?: boolean;

  className?: string;

  /**
   * The initial page to display in uncontrolled mode.
   */
  defaultActiveIndex?: number;

  /**
   * Enables drag to scroll on carousel items.
   * Defaults to: False
   */
  draggable?: Booleanish;

  /**
   * Controls the number of carousel cards per navigation element, will default to 'auto'
   * Recommended to set to '1' when using full page carousel cards.
   */
  groupSize?: number | "auto";

  /**
   * Sets motion type as either 'slide' or 'fade'
   * Defaults: 'slide'
   *
   * Users can also pass 'slide' & duration via CarouselMotion object to control carousel speed.
   * Drag interactions are not affected because duration is then determined by the drag force.
   *
   * Note: Duration is not in milliseconds because Carousel uses an
   * attraction physics simulation when scrolling instead of easings.
   * Only values between 20-60 are recommended, 25 is the default.
   */
  motion?: CarouselMotion;

  /**
   * Callback to notify a page change.
   */
  onActiveIndexChange?: EventHandler<CarouselIndexChangeData>;

  ref?: Ref<HTMLDivElement>;

  style?: CSSProperties;

  /**
   * Adds whitespace to start/end so that 'align' prop is always respected for current index
   * Defaults to: False
   */
  whitespace?: boolean;
}
```

### CarouselAutoplayButton API

If the Carousel is on auto-play, the user may opt into pausing the auto-play feature via the
CarouselAutoplayButton which must be present for auto-play to be enabled.

If CarouselAutoplayButton is present, auto-play will default to true on mount.

```ts
interface CarouselAutoplayButtonProps {
  /**
   * A button can have its content and borders styled for greater emphasis or to be subtle.
   * - 'secondary' (default): Gives emphasis to the button in such a way that it indicates a secondary action.
   * - 'primary': Emphasizes the button as a primary action.
   * - 'outline': Removes background styling.
   * - 'subtle': Minimizes emphasis to blend into the background until hovered or focused.
   * - 'transparent': Removes background and border styling.
   * @default 'secondary'
   */
  appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent";

  as?: "a" | "button";

  /**
   * Defines the controlled checked state of the `ToggleButton`.
   * If passed, `ToggleButton` ignores the `defaultChecked` property.
   * This should only be used if the checked state is to be controlled at a higher level and there is a plan to pass the
   * correct value based on handling `onClick` events and re-rendering.
   * @default false
   */
  checked?: boolean;

  children?: any;

  className?: string;

  /**
   * Defines whether the `ToggleButton` is initially in a checked state or not when rendered.
   * @default false
   */
  defaultChecked?: boolean;

  /**
   * A button can show that it cannot be interacted with.
   * @default false
   */
  disabled?: boolean;

  /**
   * When set, allows the button to be focusable even when it has been disabled.
   * This is used in scenarios where it is important to keep a consistent tab order
   * for screen reader and keyboard users. The primary example of this
   * pattern is when the disabled button is in a menu or a commandbar and is seldom used for standalone buttons.
   * When set, allows the button to be focusable even when it has been disabled. This is used in scenarios where it
   * is important to keep a consistent tab order for screen reader and keyboard users. The primary example of this
   * pattern is when the disabled button is in a menu or a commandbar and is seldom used for standalone buttons.
   * @default false
   * false
   */
  disabledFocusable?: boolean;

  /**
   * Icon that renders either before or after the `children` as specified by the `iconPosition` prop.
   */
  icon?: WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null | undefined;

  /**
   * A button can format its icon to appear before or after its content.
   * @default 'before'
   */
  iconPosition?: "before" | "after";

  /**
   * Defines whether the `ToggleButton` should use the alternate selected styles that have adequate contrast with the rest style
   * @default false
   */
  isAccessible?: boolean;

  /**
   * Callback that informs the user when internal autoplay value has changed
   */
  onCheckedChange?: EventHandler<CarouselAutoplayChangeData>;

  ref?: Ref<HTMLAnchorElement | HTMLButtonElement>;

  /**
   * A button can be rounded, circular, or square.
   * @default 'rounded'
   */
  shape?: "circular" | "rounded" | "square";

  /**
   * A button supports different sizes.
   * @default 'medium'
   */
  size?: "small" | "medium" | "large";

  style?: CSSProperties;
}
```

### CarouselButton API

A default navigation button that will set value to the next/previous page,
driven by it's type 'next' or 'previous'.

```ts
interface CarouselButtonProps {
  /**
   * A button can have its content and borders styled for greater emphasis or to be subtle.
   * - 'secondary' (default): Gives emphasis to the button in such a way that it indicates a secondary action.
   * - 'primary': Emphasizes the button as a primary action.
   * - 'outline': Removes background styling.
   * - 'subtle': Minimizes emphasis to blend into the background until hovered or focused.
   * - 'transparent': Removes background and border styling.
   * @default 'secondary'
   */
  appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent";

  as?: "a" | "button";

  children?: any;

  className?: string;

  /**
   * A button can show that it cannot be interacted with.
   * @default false
   */
  disabled?: boolean;

  /**
   * When set, allows the button to be focusable even when it has been disabled.
   * This is used in scenarios where it is important to keep a consistent tab order
   * for screen reader and keyboard users. The primary example of this
   * pattern is when the disabled button is in a menu or a commandbar and is seldom used for standalone buttons.
   * When set, allows the button to be focusable even when it has been disabled. This is used in scenarios where it
   * is important to keep a consistent tab order for screen reader and keyboard users. The primary example of this
   * pattern is when the disabled button is in a menu or a commandbar and is seldom used for standalone buttons.
   * @default false
   * false
   */
  disabledFocusable?: boolean;

  /**
   * Icon that renders either before or after the `children` as specified by the `iconPosition` prop.
   */
  icon?: WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null | undefined;

  /**
   * A button can format its icon to appear before or after its content.
   * @default 'before'
   */
  iconPosition?: "before" | "after";

  /**
   * Dictates whether button will be of type go next or go previous
   * Default: 'next'
   */
  navType?: "next" | "prev";

  ref?: Ref<HTMLAnchorElement | HTMLButtonElement>;

  /**
   * A button can be rounded, circular, or square.
   * @default 'rounded'
   */
  shape?: "circular" | "rounded" | "square";

  /**
   * A button supports different sizes.
   * @default 'medium'
   */
  size?: "small" | "medium" | "large";

  style?: CSSProperties;
}
```

### CarouselCard API

The defining wrapper of a carousel's indexed content, they will take up the full
viewport of CarouselSlider or div wrapper,
users may place multiple items within this Card if desired, with consideration of viewport width.

Clickable actions within the content area are available via mouse and tab as expected,
non-active card content will be set to inert until moved to active card.

```ts
interface CarouselCardProps {
  as?: "div";

  /**
   * Sets the card styling to be responsive based on content.
   */
  autoSize?: boolean;

  children?: any;

  className?: string;

  ref?: Ref<HTMLDivElement>;

  style?: CSSProperties;
}
```

### CarouselNav API

Used to jump to a card based on index, using arrow navigation via Tabster.

The children of this component will be wrapped in a context to
provide the appropriate value based on their index position.

```ts
interface CarouselNavProps {
  /**
   * Enables an alternate brand style when set to 'brand'
   */
  appearance?: "brand";

  as?: "div";

  children: NavButtonRenderFunction;

  className?: string;

  ref?: Ref<HTMLDivElement>;

  style?: CSSProperties;

  /**
   * The total number of slides available.
   * Users may override if using the component without a Carousel wrapper or implementing custom functionality.
   */
  totalSlides?: number;
}
```

### CarouselNavButton API

The child element of CarouselNav, a singular button that will set the carousels active value on click.

```ts
interface CarouselNavButtonProps {
  as?: "a" | "button" | "div";

  children?: any;

  className?: string;

  disabled?: boolean;

  /**
   * When set, allows the button to be focusable even when it has been disabled.
   * This is used in scenarios where it is important to keep a consistent tab order
   * for screen reader and keyboard users. The primary example of this
   * pattern is when the disabled button is in a menu or a commandbar and is seldom used for standalone buttons.
   * @default false
   */
  disabledFocusable?: boolean;

  ref?: Ref<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement>;

  style?: CSSProperties;
}
```

### CarouselNavContainer API

CarouselNavContainer component - This container will provide multiple valid layout options for the underlying carousel controls

```ts
interface CarouselNavContainerProps {
  as?: "div";

  autoplay?: WithSlotShorthandValue<(Omit<ButtonSlots, "root"> & Omit<{ as?: "button"; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & { ref?: ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent" | undefined; disabledFocusable?: boolean | undefined; disabled?: boolean | undefined; iconPosition?: "before" | "after" | undefined; shape?: "circular" | "rounded" | "square" | undefined; size?: ButtonSize | undefined; } & { defaultChecked?: boolean | undefined; checked?: boolean | undefined; isAccessible?: boolean | undefined; } & Omit<CarouselAutoplayButtonSlots, "root"> & { onCheckedChange?: EventHandler<CarouselAutoplayChangeData> | undefined; } & RefAttributes<HTMLAnchorElement | HTMLButtonElement>) | (Omit<ButtonSlots, "root"> & Omit<{ as: "a"; } & Omit<Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref"> & { ref?: ((instance: HTMLAnchorElement | null) => void) | RefObject<HTMLAnchorElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent" | undefined; disabledFocusable?: boolean | undefined; disabled?: boolean | undefined; iconPosition?: "before" | "after" | undefined; shape?: "circular" | "rounded" | "square" | undefined; size?: ButtonSize | undefined; } & { defaultChecked?: boolean | undefined; checked?: boolean | undefined; isAccessible?: boolean | undefined; } & Omit<CarouselAutoplayButtonSlots, "root"> & { onCheckedChange?: EventHandler<CarouselAutoplayChangeData> | undefined; } & RefAttributes<HTMLAnchorElement | HTMLButtonElement>)> | null | undefined;

  autoplayTooltip?: WithSlotShorthandValue<TooltipProps> | null;

  children?: any;

  className?: string;

  /**
   * Default: 'inline'
   * Defines the nav container layout:
   *
   * 'inline' - Default controls inline with carousel view
   *
   *  inline-wide - Similar to inline but places nav buttons on far left/right
   *
   * 'overlay' - Controls overlaid on bottom of carousel viewport,
   *
   * 'overlay-wide' - Controls overlaid on bottom of carousel viewport with prev+autoplay/next buttons on far side
   *
   * 'overlay-expanded' - Controls overlaid on bottom of carousel viewport, with prev/next buttons on sides vertically centered
   */
  layout?: "inline" | "inline-wide" | "overlay" | "overlay-wide" | "overlay-expanded";

  next?: WithSlotShorthandValue<(Partial<Omit<ButtonSlots, "root"> & Omit<{ as?: "button"; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & { ref?: ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent" | undefined; disabledFocusable?: boolean | undefined; disabled?: boolean | undefined; iconPosition?: "before" | "after" | undefined; shape?: "circular" | "rounded" | "square" | undefined; size?: ButtonSize | undefined; }> & Omit<CarouselButtonSlots, "root"> & Omit<{ as?: "button" | undefined; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & { ref?: ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { navType?: "next" | "prev" | undefined; }) | (Partial<Omit<ButtonSlots, "root"> & Omit<{ as: "a"; } & Omit<Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref"> & { ref?: ((instance: HTMLAnchorElement | null) => void) | RefObject<HTMLAnchorElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent" | undefined; disabledFocusable?: boolean | undefined; disabled?: boolean | undefined; iconPosition?: "before" | "after" | undefined; shape?: "circular" | "rounded" | "square" | undefined; size?: ButtonSize | undefined; }> & Omit<CarouselButtonSlots, "root"> & Omit<{ as?: "button" | undefined; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & { ref?: ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { navType?: "next" | "prev" | undefined; }) | (Partial<Omit<ButtonSlots, "root"> & Omit<{ as: "a"; } & Omit<Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref"> & { ref?: ((instance: HTMLAnchorElement | null) => void) | RefObject<HTMLAnchorElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent" | undefined; disabledFocusable?: boolean | undefined; disabled?: boolean | undefined; iconPosition?: "before" | "after" | undefined; shape?: "circular" | "rounded" | "square" | undefined; size?: ButtonSize | undefined; }> & Omit<CarouselButtonSlots, "root"> & Omit<{ as: "a"; } & Omit<Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref"> & { ref?: ((instance: HTMLAnchorElement | null) => void) | RefObject<HTMLAnchorElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { navType?: "next" | "prev" | undefined; })> | null | undefined;

  nextTooltip?: WithSlotShorthandValue<TooltipProps> | null;

  prev?: WithSlotShorthandValue<(Partial<Omit<ButtonSlots, "root"> & Omit<{ as?: "button"; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & { ref?: ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent" | undefined; disabledFocusable?: boolean | undefined; disabled?: boolean | undefined; iconPosition?: "before" | "after" | undefined; shape?: "circular" | "rounded" | "square" | undefined; size?: ButtonSize | undefined; }> & Omit<CarouselButtonSlots, "root"> & Omit<{ as?: "button" | undefined; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & { ref?: ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { navType?: "next" | "prev" | undefined; }) | (Partial<Omit<ButtonSlots, "root"> & Omit<{ as: "a"; } & Omit<Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref"> & { ref?: ((instance: HTMLAnchorElement | null) => void) | RefObject<HTMLAnchorElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent" | undefined; disabledFocusable?: boolean | undefined; disabled?: boolean | undefined; iconPosition?: "before" | "after" | undefined; shape?: "circular" | "rounded" | "square" | undefined; size?: ButtonSize | undefined; }> & Omit<CarouselButtonSlots, "root"> & Omit<{ as?: "button" | undefined; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & { ref?: ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { navType?: "next" | "prev" | undefined; }) | (Partial<Omit<ButtonSlots, "root"> & Omit<{ as: "a"; } & Omit<Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref"> & { ref?: ((instance: HTMLAnchorElement | null) => void) | RefObject<HTMLAnchorElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { appearance?: "outline" | "subtle" | "secondary" | "primary" | "transparent" | undefined; disabledFocusable?: boolean | undefined; disabled?: boolean | undefined; iconPosition?: "before" | "after" | undefined; shape?: "circular" | "rounded" | "square" | undefined; size?: ButtonSize | undefined; }> & Omit<CarouselButtonSlots, "root"> & Omit<{ as: "a"; } & Omit<Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref"> & { ref?: ((instance: HTMLAnchorElement | null) => void) | RefObject<HTMLAnchorElement> | null | undefined; }, "children"> & { children?: any; } & Pick<ARIAButtonProps<ARIAButtonType>, "disabled" | "disabledFocusable">, "ref"> & { navType?: "next" | "prev" | undefined; })> | null | undefined;

  prevTooltip?: WithSlotShorthandValue<TooltipProps> | null;

  ref?: Ref<HTMLDivElement>;

  style?: CSSProperties;
}
```

### CarouselNavImageButton API

A variant child element of CarouselNav, a singular image button that displays a
preview of card content and will set the carousels active value on click.

```ts
interface CarouselNavImageButtonProps {
  as?: "a" | "button" | "div";

  children?: any;

  className?: string;

  disabled?: boolean;

  /**
   * When set, allows the button to be focusable even when it has been disabled.
   * This is used in scenarios where it is important to keep a consistent tab order
   * for screen reader and keyboard users. The primary example of this
   * pattern is when the disabled button is in a menu or a commandbar and is seldom used for standalone buttons.
   * @default false
   */
  disabledFocusable?: boolean;

  /**
   * Required: The image within the button
   */
  image: { as?: "img" | undefined; } & Omit<Omit<DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "ref"> & { ref?: ((instance: HTMLImageElement | null) => void) | RefObject<HTMLImageElement> | null | undefined; }, "children"> & { children?: undefined; };

  ref?: Ref<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement>;

  style?: CSSProperties;
}
```

### CarouselSlider API

CarouselSlider component - The viewport window that CarouselCards are contained within.

```ts
interface CarouselSliderProps {
  as?: "div";

  /**
   * cardFocus sets the carousel slider as a focus group,
   * enabling left/right navigation of elements.
   *
   * This will also be passed into CarouselCards via context and set the appropriate focus attributes
   *
   * Defaults: false
   */
  cardFocus?: boolean;

  children?: any;

  className?: string;

  ref?: Ref<HTMLDivElement>;

  style?: CSSProperties;
}
```

### CarouselViewport API

CarouselViewport component - TODO: add more docs

```ts
interface CarouselViewportProps {
  as?: "div";

  children?: any;

  className?: string;

  ref?: Ref<HTMLDivElement>;

  style?: CSSProperties;
}
```

## Implementation

CoreAI 0.12.0; Fluent 9.74.9.

[Setup and dependencies](/coreai-design-system/guides/Package.md).

Import components from `@coreai-ds/react-components`. CoreAI guidance above takes precedence over API demonstration examples.

## Alignment And Whitespace

Source: Fluent, adapted for CoreAI.

Carousel can have slides aligned relative to the carousel viewport, use the `align` prop to set the alignment. Note, the `whitespace` prop could be used to clear leading and trailing empty space that causes excessive scrolling.

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--alignment-and-whitespace.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import * as React from 'react';
import type { CarouselProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import { Switch } from "@coreai-ds/react-components";
import { Carousel } from "@coreai-ds/react-components";
import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import { Image } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { DocumentLinkRegular } from "@fluentui/react-icons";
import { Persona } from "@coreai-ds/react-components";
import { MoreHorizontalRegular } from "@fluentui/react-icons";
import { CarouselNavContainer } from "@coreai-ds/react-components";
import { CarouselNav } from "@coreai-ds/react-components";
import { CarouselNavButton } from "@coreai-ds/react-components";

const useClasses = makeStyles({
    container: {
        display: 'grid',
        gridTemplateColumns: 'minmax(0, 1fr)',
        gridTemplateRows: 'auto 1fr',
        boxShadow: tokens.shadow16,
    },
    card: {
        display: 'flex',
        flexDirection: 'column',
        border: `${tokens.strokeWidthThicker} solid ${tokens.colorNeutralForeground3}`,
        borderRadius: tokens.borderRadiusMedium,
        borderTopLeftRadius: 0,
        borderTopRightRadius: 0,
        padding: '10px',
        minHeight: '100px',
    },
    carousel: {
        flex: 1,
        padding: '20px',
    },
    controls: {
        display: 'flex',
        flexDirection: 'column',
        gap: '6px',
        border: `${tokens.strokeWidthThicker} solid ${tokens.colorNeutralForeground3}`,
        borderBottom: 'none',
        borderRadius: tokens.borderRadiusMedium,
        borderBottomLeftRadius: 0,
        borderBottomRightRadius: 0,
        padding: '10px',
    },
    field: {
        flex: 1,
        gridTemplateColumns: 'minmax(100px, max-content) 1fr',
    },
    dropdown: {
        minWidth: '100px',
        width: '1fr',
    },
});

const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => {
    return `Carousel slide ${index + 1} of ${totalSlides}`;
};

type Post = {
    avatarUrl: string;
    name: string;
    text: string;
    description: string;
};

const POSTS: Post[] = [
    {
        avatarUrl: 'https://fabricweb.azureedge.net/fabric-website/assets/images/avatar/AllanMunger.jpg',
        name: 'Allan Munger',
        text: 'Meeting notes',
        description: '2 days ago by Kathryn Murphy',
    },
    {
        avatarUrl: 'https://fabricweb.azureedge.net/fabric-website/assets/images/avatar/AmandaBrady.jpg',
        name: 'Amanda Brady',
        text: 'FY24 Hiring Budget',
        description: 'Wed at 3:38pm',
    },
    {
        avatarUrl: 'https://fabricweb.azureedge.net/fabric-website/assets/images/avatar/AshleyMcCarthy.jpg',
        name: 'Ashley McCarthy',
        text: 'Test edited this',
        description: 'Thu at 4:38pm',
    },
    {
        avatarUrl: 'https://fabricweb.azureedge.net/fabric-website/assets/images/avatar/CameronEvans.jpg',
        name: 'Cameron Evans',
        text: 'Review 1:1 Recap',
        description: 'You recently opened this',
    },
    {
        avatarUrl: 'https://fabricweb.azureedge.net/fabric-website/assets/images/avatar/CarlosSlattery.jpg',
        name: 'Carlos Slattery',
        text: 'FY24 Hiring Test',
        description: '2 days ago by Cecil Folk',
    },
];

const useCardClasses = makeStyles({
    actionCard: {
        borderRadius: tokens.borderRadiusLarge,
        overflow: 'hidden',
        textAlign: 'center',
        maxWidth: '350px',
        margin: '0px 6px',
        boxShadow: tokens.shadow16,
    },
    imageContainer: {
        position: 'relative',
    },
    image: {
        maxHeight: '200px',
    },
    imageButton: {
        position: 'absolute',
        left: '12px',
        bottom: '12px',
    },
    info: {
        display: 'flex',
        flexDirection: 'row',
        padding: '12px',
        width: 'auto',
        alignContent: 'center',
        justifyContent: 'space-between',
    },
});

const swapImage = 'https://fabricweb.azureedge.net/fabric-website/assets/images/wireframe/image-square.png';

const ActionCard: React.FC<Post & {
    index: number;
}> = props => {
    const { avatarUrl, description, name, text, index } = props;
    const classes = useCardClasses();
    return (<CarouselCard className={classes.actionCard} aria-label={`Card ${index + 1} of ${POSTS.length}`}>
      <div className={classes.imageContainer}>
        <Image className={classes.image} fit="cover" src={swapImage} role="presentation"/>
        <Button className={classes.imageButton} icon={<DocumentLinkRegular />} aria-label="Go to document"/>
      </div>
      <div className={classes.info}>
        <Persona textPosition="after" avatar={{ name, image: { src: avatarUrl } }} name={text} presence={{ status: 'available' }} secondaryText={description}/>
        <Button icon={<MoreHorizontalRegular />} aria-label="More options"/>
      </div>
    </CarouselCard>);
};

export const AlignmentAndWhitespace = (): JSXElement => {
    const classes = useClasses();
    const [alignment, setAlignment] = React.useState<CarouselProps['align']>('center');
    const [whitespace, setWhitespace] = React.useState<boolean>(false);
    return (<div className={classes.container}>
      <div className={classes.controls}>
        <Field label="Alignment" orientation="horizontal" className={classes.field}>
          <Dropdown className={classes.dropdown} placeholder="Select an alignment" onOptionSelect={(_, option) => {
            setAlignment(option.optionText as CarouselProps['align']);
        }} value={alignment}>
            {['start', 'center', 'end'].map(option => (<Option key={option}>{option}</Option>))}
          </Dropdown>
        </Field>

        <Field label="Whitespace" orientation="horizontal" className={classes.field}>
          <Switch checked={whitespace} onChange={() => setWhitespace(!whitespace)}/>
        </Field>
      </div>

      <div className={classes.card}>
        <Carousel align={alignment} className={classes.carousel} whitespace={whitespace} announcement={getAnnouncement}>
          <CarouselViewport>
            <CarouselSlider cardFocus aria-label="Use the left and right arrow keys to navigate focused carousel card">
              {POSTS.map((post, index) => (<ActionCard {...post} key={post.name} index={index}/>))}
            </CarouselSlider>
          </CarouselViewport>
          <CarouselNavContainer layout="inline" next={{ 'aria-label': 'go to next' }} prev={{ 'aria-label': 'go to prev' }}>
            <CarouselNav>{index => <CarouselNavButton aria-label={`Carousel Nav Button ${index}`}/>}</CarouselNav>
          </CarouselNavContainer>
        </Carousel>
      </div>
    </div>);
};

export default AlignmentAndWhitespace;
```

## Appearance

Source: Fluent, adapted for CoreAI.

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--appearance.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { Carousel } from "@coreai-ds/react-components";
import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { typographyStyles } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import { Image } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { CarouselNavContainer } from "@coreai-ds/react-components";
import { CarouselNav } from "@coreai-ds/react-components";
import { CarouselNavButton } from "@coreai-ds/react-components";
const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number) => {
    return `Elevated carousel slide ${index + 1} of ${totalSlides}`;
};
const IMAGES = [
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg',
];
const useClasses = makeStyles({
    bannerCard: {
        alignContent: 'center',
        height: '450px',
        textAlign: 'left',
        position: 'relative',
    },
    cardContainer: {
        display: 'flex',
        flexDirection: 'column',
        gap: '8px',
        position: 'absolute',
        left: '10%',
        top: '25%',
        background: tokens.colorNeutralBackground1,
        padding: '18px',
        maxWidth: '270px',
        width: '50%',
    },
    title: {
        ...typographyStyles.title1,
    },
    subtext: {
        ...typographyStyles.body1,
    },
});
const BannerCard: React.FC<{
    children: React.ReactNode;
    imageSrc: string;
    index: number;
}> = props => {
    const { children, imageSrc, index } = props;
    const classes = useClasses();
    return (<CarouselCard className={classes.bannerCard} aria-label={`${index + 1} of ${IMAGES.length}`} id={`components-carousel-carousel--appearance-test-${index}`}>
      <Image fit="cover" src={imageSrc} role="presentation"/>

      <div className={classes.cardContainer}>
        <div className={classes.title}>{children}</div>
        <div className={classes.subtext}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
          magna aliqua. Ut enim ad minim veniam.
        </div>
        <div>
          <Button size="small" shape="square" appearance="primary">
            Call to action
          </Button>
        </div>
      </div>
    </CarouselCard>);
};
export const Appearance = (): JSXElement => (<Carousel appearance="elevated" groupSize={1} circular announcement={getAnnouncement}>
    <CarouselViewport>
      <CarouselSlider>
        {IMAGES.map((imageSrc, index) => (<BannerCard key={`image-${index}`} imageSrc={imageSrc} index={index}>
            Card {index + 1}
          </BannerCard>))}
      </CarouselSlider>
    </CarouselViewport>
    <CarouselNavContainer layout="inline" autoplayTooltip={{ content: 'Autoplay', relationship: 'label' }} nextTooltip={{ content: 'Go to next', relationship: 'label' }} prevTooltip={{ content: 'Go to prev', relationship: 'label' }}>
      <CarouselNav>{index => <CarouselNavButton aria-label={`Carousel Nav Button ${index}`}/>}</CarouselNav>
    </CarouselNavContainer>
  </Carousel>);

export default Appearance;
```

## Autoplay

Source: Fluent, adapted for CoreAI.

The Autoplay button must be present to enable autoplay as it is an accessibility requirement. To enable, any valid prop (recommended ariaLabel) must be passed in, while setting the autoplay prop in CarouselNav to undefined will disable and remove it. The `autoplayInterval` prop controls the delay between slide transitions in milliseconds (defaults to 4000ms).

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--autoplay.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { typographyStyles } from "@coreai-ds/react-components";
import * as React from 'react';
import type { CarouselAutoplayButtonProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { Switch } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import { Carousel } from "@coreai-ds/react-components";
import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import { Image } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { CarouselNavContainer } from "@coreai-ds/react-components";
import { CarouselNav } from "@coreai-ds/react-components";
import { CarouselNavButton } from "@coreai-ds/react-components";

const useClasses = makeStyles({
    bannerCard: {
        alignContent: 'center',
        borderRadius: tokens.borderRadiusLarge,
        height: '450px',
        textAlign: 'left',
        position: 'relative',
    },
    cardContainer: {
        display: 'flex',
        flexDirection: 'column',
        gap: '8px',
        position: 'absolute',
        left: '10%',
        top: '25%',
        background: tokens.colorNeutralBackground1,
        padding: '18px',
        maxWidth: '270px',
        width: '50%',
    },
    title: {
        ...typographyStyles.title1,
    },
    subtext: {
        ...typographyStyles.body1,
    },
    container: {
        display: 'grid',
        gridTemplateColumns: '1fr',
        gridTemplateRows: 'auto 1fr',
        boxShadow: tokens.shadow16,
    },
    card: {
        display: 'flex',
        flexDirection: 'column',
        border: `${tokens.strokeWidthThicker} solid ${tokens.colorNeutralForeground3}`,
        borderRadius: tokens.borderRadiusMedium,
        borderTopLeftRadius: 0,
        borderTopRightRadius: 0,
        padding: '10px',
        minHeight: '100px',
    },
    carousel: {
        flex: 1,
        padding: '20px',
    },
    controls: {
        display: 'flex',
        flexDirection: 'column',
        gap: '6px',
        border: `${tokens.strokeWidthThicker} solid ${tokens.colorNeutralForeground3}`,
        borderBottom: 'none',
        borderRadius: tokens.borderRadiusMedium,
        borderBottomLeftRadius: 0,
        borderBottomRightRadius: 0,
        padding: '10px',
    },
    field: {
        flex: 1,
        gridTemplateColumns: 'minmax(140px, max-content) 1fr',
    },
    dropdown: {
        maxWidth: 'max-content',
    },
});

const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => {
    return `Carousel slide ${index + 1} of ${totalSlides}`;
};

const IMAGES = [
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg',
];

const BannerCard: React.FC<{
    children: React.ReactNode;
    imageSrc: string;
    index: number;
}> = props => {
    const { children, imageSrc, index } = props;
    const classes = useClasses();
    return (<CarouselCard className={classes.bannerCard} aria-label={`${index + 1} of ${IMAGES.length}`}>
      <Image fit="cover" src={imageSrc} role="presentation"/>

      <div className={classes.cardContainer}>
        <div className={classes.title}>{children}</div>
        <div className={classes.subtext}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
          magna aliqua. Ut enim ad minim veniam.
        </div>
        <div>
          <Button size="small" shape="square" appearance="primary">
            Call to action
          </Button>
        </div>
      </div>
    </CarouselCard>);
};

export const Autoplay = (): JSXElement => {
    const classes = useClasses();
    const [autoplayEnabled, setAutoplayEnabled] = React.useState(false);
    const [autoplayButton, setAutoplayButton] = React.useState(true);
    const [autoplayInterval, setAutoplayInterval] = React.useState(4000);
    const autoplayProps: CarouselAutoplayButtonProps | undefined = autoplayButton
        ? {
            'aria-label': 'Enable autoplay',
            checked: autoplayEnabled,
            onCheckedChange: (e, data) => {
                setAutoplayEnabled(data.checked);
            },
        }
        : undefined;
    return (<div className={classes.container}>
      <div className={classes.controls}>
        <Field label="Autoplay Present" orientation="horizontal" className={classes.field}>
          <Switch checked={autoplayButton} onChange={() => setAutoplayButton(!autoplayButton)}/>
        </Field>

        <Field label="Autoplay Enabled" orientation="horizontal" className={classes.field}>
          <Switch checked={autoplayEnabled} onChange={() => setAutoplayEnabled(!autoplayEnabled)}/>
        </Field>

        <Field label="Autoplay Interval (ms)" orientation="horizontal" className={classes.field} hint={`Delay between slides: ${autoplayInterval}ms`}>
          <Input type="number" value={autoplayInterval.toString()} onChange={(e, data) => {
            const value = parseInt(data.value, 10);
            if (!isNaN(value) && value >= 1000) {
                setAutoplayInterval(value);
            }
        }} min="1000" step="1000"/>
        </Field>
      </div>
      <div className={classes.card}>
        <Carousel groupSize={1} circular announcement={getAnnouncement} autoplayInterval={autoplayInterval}>
          <CarouselViewport>
            <CarouselSlider>
              {IMAGES.map((imageSrc, index) => (<BannerCard key={`image-${index}`} imageSrc={imageSrc} index={index}>
                  Card {index + 1}
                </BannerCard>))}
            </CarouselSlider>
          </CarouselViewport>
          <CarouselNavContainer layout="inline" autoplay={autoplayProps} next={{ 'aria-label': 'go to next' }} prev={{ 'aria-label': 'go to prev' }}>
            <CarouselNav>{index => <CarouselNavButton aria-label={`Carousel Nav Button ${index}`}/>}</CarouselNav>
          </CarouselNavContainer>
        </Carousel>
      </div>
    </div>);
};

export default Autoplay;
```

## Controlled

Source: Fluent, adapted for CoreAI.

Carousel can be controlled by setting `activeIndex` and `onActiveIndexChange` props.

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--controlled.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Carousel } from "@coreai-ds/react-components";
import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { Tooltip } from "@coreai-ds/react-components";
import { CarouselButton } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import { mergeClasses } from "@coreai-ds/react-components";
import { Title1 } from "@coreai-ds/react-components";
import { Body1 } from "@coreai-ds/react-components";
import { CarouselAutoplayButton } from "@coreai-ds/react-components";
import { Divider } from "@coreai-ds/react-components";
import { Toolbar } from "@coreai-ds/react-components";
import { ToolbarButton } from "@coreai-ds/react-components";

const useClasses = makeStyles({
    carousel: {
        display: 'grid',
        gridTemplateColumns: '1fr auto 1fr',
        gridTemplateRows: '1fr auto',
        gap: '10px',
        placeItems: 'center',
    },
    container: {
        display: 'flex',
        flexDirection: 'column',
        gap: '20px',
    },
    viewport: {
        overflow: 'hidden',
    },
    footer: {
        display: 'flex',
        gap: '10px',
        alignSelf: 'center',
        justifySelf: 'center',
        width: 'max-content',
        border: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`,
        borderRadius: tokens.borderRadiusMedium,
        boxShadow: tokens.shadow16,
        padding: '10px',
    },
    controls: {
        padding: 0,
    },
    controlButton: {
        minWidth: '32px',
    },
    code: {
        display: 'flex',
        placeItems: 'center',
        padding: '4px 8px',
        fontSize: tokens.fontSizeBase200,
        lineHeight: tokens.lineHeightBase200,
        backgroundColor: tokens.colorNeutralBackground4,
        borderRadius: tokens.borderRadiusMedium,
    },
    wireframe: {
        backgroundColor: tokens.colorNeutralBackground3,
        border: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`,
        display: 'flex',
        flexDirection: 'column',
        gap: '12px',
        placeContent: 'center',
        padding: '40px',
        minHeight: '200px',
        position: 'relative',
    },
    wireframeEven: {
        backgroundColor: tokens.colorBrandBackground2,
        border: `${tokens.strokeWidthThin} solid ${tokens.colorBrandStroke1}`,
    },
    wireframeInfo: {
        position: 'absolute',
        right: '12px',
        top: '12px',
        backgroundColor: tokens.colorPaletteRedBackground2,
        border: `${tokens.strokeWidthThin} dotted ${tokens.colorPaletteRedBorder2}`,
        fontSize: tokens.fontSizeBase200,
        padding: '4px 8px',
    },
});

const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => {
    return `Carousel slide ${index + 1} of ${totalSlides}`;
};

const WireframeContent: React.FC<{
    index: number;
}> = props => {
    const classes = useClasses();
    return (<div className={mergeClasses(classes.wireframe, props.index % 2 === 0 && classes.wireframeEven)}>
      <div className={classes.wireframeInfo}>
        <code>index: {props.index}</code>
      </div>
      <Title1 align="center">Lorem Ipsum</Title1>
      <Body1 align="center">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...</Body1>
    </div>);
};

export const Controlled = (): JSXElement => {
    const [activeIndex, setActiveIndex] = React.useState(1);
    const classes = useClasses();
    return (<div className={classes.container}>
      <Carousel activeIndex={activeIndex} groupSize={1} draggable onActiveIndexChange={(e, data) => setActiveIndex(data.index)} announcement={getAnnouncement}>
        <div className={classes.carousel}>
          <Tooltip content="Go To Previous Page" relationship="label">
            <CarouselButton navType="prev" aria-label="Previous Carousel Page Button"/>
          </Tooltip>

          <CarouselViewport className={classes.viewport}>
            <CarouselSlider>
              <CarouselCard aria-label="1 of 5">
                <WireframeContent index={0}/>
              </CarouselCard>
              <CarouselCard aria-label="2 of 5">
                <WireframeContent index={1}/>
              </CarouselCard>
              <CarouselCard aria-label="3 of 5">
                <WireframeContent index={2}/>
              </CarouselCard>
              <CarouselCard aria-label="4 of 5">
                <WireframeContent index={3}/>
              </CarouselCard>
              <CarouselCard aria-label="5 of 5">
                <WireframeContent index={4}/>
              </CarouselCard>
            </CarouselSlider>
          </CarouselViewport>

          <Tooltip content="Go To Next Page" relationship="label">
            <CarouselButton navType="next" aria-label="Next Carousel Page Button"/>
          </Tooltip>
        </div>

        <div className={classes.footer}>
          <CarouselAutoplayButton aria-label="Enable autoplay"/>
          <Divider vertical/>
          <code className={classes.code}>{JSON.stringify({ activeIndex }, null, 2)}</code>
          <Divider vertical/>
          <Toolbar className={classes.controls}>
            {new Array(5).fill(null).map((_, index) => (<ToolbarButton key={`toolbar-button-${index}`} aria-label={`Carousel Nav Button ${index} `} className={classes.controlButton} appearance="subtle" disabled={index === activeIndex} onClick={() => setActiveIndex(index)}>
                {index}
              </ToolbarButton>))}
          </Toolbar>
        </div>
      </Carousel>
    </div>);
};

export default Controlled;
```

## Eventing

Source: Fluent, adapted for CoreAI.

Carousel provides callbacks on index change with a multitude of event types.

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--eventing.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import * as React from 'react';
import { Carousel } from "@coreai-ds/react-components";
import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import { mergeClasses } from "@coreai-ds/react-components";
import { Title1 } from "@coreai-ds/react-components";
import { Body1 } from "@coreai-ds/react-components";
import { Subtitle2 } from "@coreai-ds/react-components";
import { Caption1 } from "@coreai-ds/react-components";
import { CarouselNavContainer } from "@coreai-ds/react-components";
import { CarouselNav } from "@coreai-ds/react-components";
import { CarouselNavButton } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";

const useClasses = makeStyles({
    container: {
        display: 'flex',
        gap: '20px',
    },
    carousel: {
        border: `2px solid ${tokens.colorNeutralStroke1}`,
        borderRadius: tokens.borderRadiusMedium,
        boxShadow: tokens.shadow16,
        padding: '20px 0',
        marginTop: '24px',
    },
    card: {
        margin: '10px',
    },
    logLabel: {
        alignSelf: 'end',
        color: tokens.colorNeutralForegroundOnBrand,
        backgroundColor: tokens.colorBrandBackground,
        width: 'fit-content',
        fontWeight: tokens.fontWeightBold,
        padding: '2px 12px',
        borderRadius: `${tokens.borderRadiusMedium} ${tokens.borderRadiusMedium} 0 0`,
    },
    log: {
        overflowY: 'auto',
        boxShadow: tokens.shadow16,
        minWidth: '240px',
        flex: 1,
        border: `2px solid ${tokens.colorBrandBackground}`,
        borderRadius: `${tokens.borderRadiusMedium} 0 ${tokens.borderRadiusMedium} ${tokens.borderRadiusMedium}`,
        padding: '12px',
        maxHeight: '250px',
    },
    logContainer: {
        display: 'flex',
        flexDirection: 'column',
        height: '100%',
        filter: `drop-shadow(0 0 4px ${tokens.colorNeutralStroke1})`,
    },
    wireframe: {
        backgroundColor: tokens.colorNeutralBackground3,
        border: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`,
        display: 'flex',
        flexDirection: 'column',
        gap: '12px',
        placeContent: 'center',
        padding: '40px',
        height: '100px',
        position: 'relative',
    },
    wireframeEven: {
        backgroundColor: tokens.colorBrandBackground2,
        border: `${tokens.strokeWidthThin} solid ${tokens.colorBrandStroke1}`,
    },
    wireframeInfo: {
        position: 'absolute',
        right: '12px',
        top: '12px',
        backgroundColor: tokens.colorPaletteRedBackground2,
        border: `${tokens.strokeWidthThin} dotted ${tokens.colorPaletteRedBorder2}`,
        fontSize: tokens.fontSizeBase200,
        padding: '4px 8px',
    },
    wireframeSmall: {
        minWidth: '100px',
        padding: '40px 20px',
    },
    wireframeMedium: {
        minWidth: '200px',
        padding: '40px 20px',
    },
    wireframeLarge: {
        minWidth: '350px',
    },
});

const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => {
    return `Carousel slide ${index + 1} of ${totalSlides}`;
};

const WireframeContent: React.FC<{
    appearance: 'odd' | 'even';
    children: React.ReactNode;
    size?: 'small' | 'medium' | 'large';
}> = props => {
    const classes = useClasses();
    return (<div tabIndex={0} className={mergeClasses(classes.wireframe, props.appearance === 'even' && classes.wireframeEven, props.size === 'small' && classes.wireframeSmall, props.size === 'medium' && classes.wireframeMedium, props.size === 'large' && classes.wireframeLarge)}>
      <div className={classes.wireframeInfo}>
        <code>size: {props.size ?? 'auto'}</code>
      </div>
      {props.children}
    </div>);
};

export const Eventing = (): JSXElement => {
    const classes = useClasses();
    const labelId = useId();
    const [activeIndex, setActiveIndex] = React.useState(0);
    const [statusLog, setStatusLog] = React.useState<[
        number,
        {
            type: 'click' | 'focus' | 'drag' | 'autoplay' | undefined;
            index: number;
        }
    ][]>([]);
    return (<div className={classes.container}>
      <Carousel className={classes.carousel} groupSize={1} circular draggable announcement={getAnnouncement} activeIndex={activeIndex} onActiveIndexChange={(ev, data) => {
            setActiveIndex(data.index);
            setStatusLog(prev => [[Date.now(), { type: data.type, index: data.index }], ...prev]);
        }}>
        <CarouselViewport>
          <CarouselSlider cardFocus>
            <CarouselCard className={classes.card} autoSize aria-label="1 of 7">
              <WireframeContent appearance="odd">
                <Title1 align="center">Lorem Ipsum</Title1>
                <Body1 align="center">
                  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...
                </Body1>
              </WireframeContent>
            </CarouselCard>
            <CarouselCard className={classes.card} autoSize aria-label="2 of 7">
              <WireframeContent appearance="even" size="small">
                <Subtitle2 align="center">Lorem Ipsum</Subtitle2>
                <Caption1 align="center">Lorem ipsum...</Caption1>
              </WireframeContent>
            </CarouselCard>
            <CarouselCard className={classes.card} autoSize aria-label="3 of 7">
              <WireframeContent appearance="odd" size="medium">
                <Title1 align="center">Lorem Ipsum</Title1>
                <Caption1 align="center">Lorem ipsum dolor sit amet...</Caption1>
              </WireframeContent>
            </CarouselCard>
            <CarouselCard className={classes.card} autoSize aria-label="4 of 7">
              <WireframeContent appearance="even" size="large">
                <Title1 align="center">Lorem Ipsum</Title1>
                <Body1 align="center">Lorem ipsum dolor sit amet...</Body1>
              </WireframeContent>
            </CarouselCard>
            <CarouselCard className={classes.card} autoSize aria-label="5 of 7">
              <WireframeContent appearance="odd" size="medium">
                <Title1 align="center">Lorem Ipsum</Title1>
                <Caption1 align="center">Lorem ipsum dolor sit amet...</Caption1>
              </WireframeContent>
            </CarouselCard>
            <CarouselCard className={classes.card} autoSize aria-label="6 of 7">
              <WireframeContent appearance="even" size="large">
                <Title1 align="center">Lorem Ipsum</Title1>
                <Body1 align="center">Lorem ipsum dolor sit amet...</Body1>
              </WireframeContent>
            </CarouselCard>
            <CarouselCard className={classes.card} autoSize aria-label="7 of 7">
              <WireframeContent appearance="odd" size="small">
                <Subtitle2 align="center">Lorem Ipsum</Subtitle2>
                <Caption1 align="center">Lorem ipsum...</Caption1>
              </WireframeContent>
            </CarouselCard>
          </CarouselSlider>
        </CarouselViewport>

        <CarouselNavContainer layout="inline" next={{ 'aria-label': 'go to next' }} prev={{ 'aria-label': 'go to prev' }} autoplay={{ 'aria-label': 'Carousel autoplay' }}>
          <CarouselNav>{index => <CarouselNavButton aria-label={`Carousel Nav Button ${index}`}/>}</CarouselNav>
        </CarouselNavContainer>
      </Carousel>

      <div className={classes.logContainer}>
        <div className={classes.logLabel} id={labelId}>
          Events log
        </div>
        <div role="log" aria-labelledby={labelId} className={classes.log}>
          {statusLog.map(([time, status], i) => {
            const date = new Date(time);
            return (<div key={i}>
                {date.toLocaleTimeString()}{' '}
                <Text weight="bold">
                  {'{'} type: {status.type}, index: {status.index} {'}'}
                </Text>
              </div>);
        })}
        </div>
      </div>
    </div>);
};

export default Eventing;
```

## First Run Experience

Source: Fluent, adapted for CoreAI.

Carousel can be used in a Dialog to create a _first-run_ experience.

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--first-run-experience.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { typographyStyles } from "@coreai-ds/react-components";
import * as React from 'react';
import { Dialog } from "@coreai-ds/react-components";
import { DialogTrigger } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { DialogSurface } from "@coreai-ds/react-components";
import { Carousel } from "@coreai-ds/react-components";
import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import { Image } from "@coreai-ds/react-components";
import { CarouselNav } from "@coreai-ds/react-components";
import { CarouselNavButton } from "@coreai-ds/react-components";

const useStyles = makeStyles({
    surface: {
        padding: 0,
        border: 'none',
        overflow: 'hidden',
    },
    carousel: { padding: 0 },
    card: {},
    footer: {
        display: 'flex',
        flexDirection: 'row',
        justifyContent: 'space-between',
        width: 'auto',
        padding: `${tokens.spacingVerticalS} ${tokens.spacingVerticalXXL} ${tokens.spacingVerticalXXL} ${tokens.spacingVerticalXXL}`,
    },
    header: {
        display: 'block',
        // We use margin instead of padding to avoid messing with the focus indicator in the header
        margin: `${tokens.spacingVerticalXXL} ${tokens.spacingVerticalXXL} ${tokens.spacingVerticalS} ${tokens.spacingVerticalXXL}`,
        ...typographyStyles.subtitle1,
    },
    text: {
        display: 'block',
        padding: `${tokens.spacingVerticalS} ${tokens.spacingVerticalXXL}`,
        ...typographyStyles.body1,
    },
});

const PAGES = [
    {
        id: 'Copilot-page-1',
        alt: 'Copilot logo',
        imgSrc: 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
        header: 'Discover Copilot, a whole new way to work',
        text: 'Explore new ways to work smarter and faster using the power of AI. Copilot in [Word] can help you [get started from scratch], [work from an existing file], [get actionable insights about documents], and more.',
    },
    {
        id: 'Copilot-page-2',
        alt: 'Copilot logo 2',
        imgSrc: 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
        header: 'Use your own judgment',
        text: 'Copilot can make mistakes so remember to verify the results. To help improve the experience, please share your feedback with us.',
    },
];

const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => {
    return `Carousel slide ${index + 1} of ${totalSlides}, ${PAGES[index].header}`;
};

export const FirstRunExperience = (): JSXElement => {
    const styles = useStyles();
    const [activeIndex, setActiveIndex] = React.useState(0);
    const [open, setModalOpen] = React.useState(false);
    const totalPages = PAGES.length;
    const setPage = (page: number) => {
        if (page < 0 || page >= totalPages) {
            setModalOpen(false);
            return;
        }
        setActiveIndex(page);
    };
    React.useEffect(() => {
        // Reset or initialize page on open if necessary
        if (open) {
            setActiveIndex(0);
        }
    }, [open]);
    return (<Dialog open={open} onOpenChange={(e, data) => setModalOpen(data.open)}>
      <DialogTrigger>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogSurface className={styles.surface} aria-label="Discover Copilot">
        <Carousel className={styles.carousel} groupSize={1} circular announcement={getAnnouncement} activeIndex={activeIndex} motion="fade" onActiveIndexChange={(e, data) => setActiveIndex(data.index)}>
          <CarouselViewport>
            <CarouselSlider>
              {PAGES.map(page => (<CarouselCard className={styles.card} key={page.id}>
                  <Image src={page.imgSrc} width={600} height={324} alt={page.imgSrc}/>
                  <h1 tabIndex={-1} className={styles.header}>
                    {page.header}
                  </h1>
                  <span className={styles.text}>{page.text}</span>
                </CarouselCard>))}
            </CarouselSlider>
          </CarouselViewport>
          <div className={styles.footer}>
            <Button onClick={() => setPage(activeIndex - 1)}>{activeIndex <= 0 ? 'Not Now' : 'Previous'}</Button>

            <CarouselNav appearance="brand">
              {index => <CarouselNavButton aria-label={`Carousel Nav Button ${index}`}/>}
            </CarouselNav>

            <Button appearance="primary" onClick={() => setPage(activeIndex + 1)}>
              {activeIndex === totalPages - 1 ? 'Try Copilot' : 'Next'}
            </Button>
          </div>
        </Carousel>
      </DialogSurface>
    </Dialog>);
};

export default FirstRunExperience;
```

## Image Slideshow

Source: Fluent, adapted for CoreAI.

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--image-slideshow.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Carousel } from "@coreai-ds/react-components";
import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import * as React from 'react';
import { Image } from "@coreai-ds/react-components";
import { CarouselNavContainer } from "@coreai-ds/react-components";
import { CarouselNav } from "@coreai-ds/react-components";
import { CarouselNavImageButton } from "@coreai-ds/react-components";

const useClasses = makeStyles({
    viewport: {
        /* Optional: Prevent image from overlapping the 'overlay-expanded' controls */
        marginBottom: '72px',
    },
    card: {
        boxSizing: 'border-box',
        width: '100%',
        /*  Optional: Padding provides a buffer space for the 'overlay-expanded' next/prev buttons without cutting off viewport */
        paddingLeft: '52px',
        paddingRight: '52px',
        overflow: 'hidden',
    },
    image: {
        width: '100%',
    },
});

const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => {
    return `Carousel slide ${index + 1} of ${totalSlides}`;
};

type ImageDefinition = {
    previewUrl: string;
    url: string;
    label: string;
    disabled?: boolean;
};

const IMAGES: ImageDefinition[] = [
    {
        previewUrl: 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-swatch.jpg',
        url: 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
        label: 'sea',
    },
    {
        previewUrl: 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-swatch.jpg',
        url: 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
        label: 'bridge',
        disabled: true,
    },
    {
        previewUrl: 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-swatch.jpg',
        url: 'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg',
        label: 'park',
    },
];

const ImageCard: React.FC<ImageDefinition> = props => {
    const classes = useClasses();
    const { url } = props;
    return <Image className={classes.image} src={url} role="presentation"/>;
};

export const ImageSlideshow = (): JSXElement => {
    const classes = useClasses();
    return (<Carousel groupSize={1} align="center" announcement={getAnnouncement}>
      <CarouselViewport className={classes.viewport}>
        <CarouselSlider>
          {IMAGES.map((image, index) => (<CarouselCard key={image.url} className={classes.card} aria-label={`${index + 1} of ${IMAGES.length}`}>
              <ImageCard {...image}/>
            </CarouselCard>))}
        </CarouselSlider>
      </CarouselViewport>

      <CarouselNavContainer layout="overlay-expanded" next={{ 'aria-label': 'go to next' }} prev={{ 'aria-label': 'go to prev' }}>
        <CarouselNav>
          {index => (<CarouselNavImageButton image={{ src: IMAGES[index].previewUrl }} aria-label={`Carousel Nav Button ${index}`}/>)}
        </CarouselNav>
      </CarouselNavContainer>
    </Carousel>);
};

export default ImageSlideshow;
```

## Responsive

Source: Fluent, adapted for CoreAI.

Carousel can have responsive cards that adjust their size based on the content, using `autoSize` prop on `CarouselCard`.

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--responsive.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Carousel } from "@coreai-ds/react-components";
import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import * as React from 'react';
import { mergeClasses } from "@coreai-ds/react-components";
import { Title1 } from "@coreai-ds/react-components";
import { Body1 } from "@coreai-ds/react-components";
import { Subtitle2 } from "@coreai-ds/react-components";
import { Caption1 } from "@coreai-ds/react-components";
import { CarouselNavContainer } from "@coreai-ds/react-components";
import { CarouselNav } from "@coreai-ds/react-components";
import { CarouselNavButton } from "@coreai-ds/react-components";

const useClasses = makeStyles({
    slider: {
        gap: '10px',
    },
    wireframe: {
        backgroundColor: tokens.colorNeutralBackground3,
        border: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`,
        display: 'flex',
        flexDirection: 'column',
        gap: '12px',
        placeContent: 'center',
        padding: '40px',
        height: '200px',
        position: 'relative',
    },
    wireframeEven: {
        backgroundColor: tokens.colorBrandBackground2,
        border: `${tokens.strokeWidthThin} solid ${tokens.colorBrandStroke1}`,
    },
    wireframeInfo: {
        position: 'absolute',
        right: '12px',
        top: '12px',
        backgroundColor: tokens.colorPaletteRedBackground2,
        border: `${tokens.strokeWidthThin} dotted ${tokens.colorPaletteRedBorder2}`,
        fontSize: tokens.fontSizeBase200,
        padding: '4px 8px',
    },
    wireframeSmall: {
        minWidth: '100px',
        padding: '40px 20px',
    },
    wireframeMedium: {
        minWidth: '200px',
        padding: '40px 20px',
    },
    wireframeLarge: {
        minWidth: '350px',
    },
});

const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => {
    return `Carousel slide ${index + 1} of ${totalSlides}`;
};

const WireframeContent: React.FC<{
    appearance: 'odd' | 'even';
    children: React.ReactNode;
    size?: 'small' | 'medium' | 'large';
}> = props => {
    const classes = useClasses();
    return (<div className={mergeClasses(classes.wireframe, props.appearance === 'even' && classes.wireframeEven, props.size === 'small' && classes.wireframeSmall, props.size === 'medium' && classes.wireframeMedium, props.size === 'large' && classes.wireframeLarge)}>
      <div className={classes.wireframeInfo}>
        <code>size: {props.size ?? 'auto'}</code>
      </div>
      {props.children}
    </div>);
};

export const Responsive = (): JSXElement => {
    const classes = useClasses();
    return (<Carousel draggable announcement={getAnnouncement}>
      <CarouselViewport>
        <CarouselSlider className={classes.slider}>
          <CarouselCard autoSize aria-label="1 of 7">
            <WireframeContent appearance="odd">
              <Title1 align="center">Lorem Ipsum</Title1>
              <Body1 align="center">
                Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...
              </Body1>
            </WireframeContent>
          </CarouselCard>
          <CarouselCard autoSize aria-label="2 of 7">
            <WireframeContent appearance="even" size="small">
              <Subtitle2 align="center">Lorem Ipsum</Subtitle2>
              <Caption1 align="center">Lorem ipsum...</Caption1>
            </WireframeContent>
          </CarouselCard>
          <CarouselCard autoSize aria-label="3 of 7">
            <WireframeContent appearance="odd" size="medium">
              <Title1 align="center">Lorem Ipsum</Title1>
              <Caption1 align="center">Lorem ipsum dolor sit amet...</Caption1>
            </WireframeContent>
          </CarouselCard>
          <CarouselCard autoSize aria-label="4 of 7">
            <WireframeContent appearance="even" size="large">
              <Title1 align="center">Lorem Ipsum</Title1>
              <Body1 align="center">Lorem ipsum dolor sit amet...</Body1>
            </WireframeContent>
          </CarouselCard>
          <CarouselCard autoSize aria-label="5 of 7">
            <WireframeContent appearance="odd" size="medium">
              <Title1 align="center">Lorem Ipsum</Title1>
              <Caption1 align="center">Lorem ipsum dolor sit amet...</Caption1>
            </WireframeContent>
          </CarouselCard>
          <CarouselCard autoSize aria-label="6 of 7">
            <WireframeContent appearance="even" size="large">
              <Title1 align="center">Lorem Ipsum</Title1>
              <Body1 align="center">Lorem ipsum dolor sit amet...</Body1>
            </WireframeContent>
          </CarouselCard>
          <CarouselCard autoSize aria-label="7 of 7">
            <WireframeContent appearance="odd" size="small">
              <Subtitle2 align="center">Lorem Ipsum</Subtitle2>
              <Caption1 align="center">Lorem ipsum...</Caption1>
            </WireframeContent>
          </CarouselCard>
        </CarouselSlider>
      </CarouselViewport>

      <CarouselNavContainer layout="inline" next={{ 'aria-label': 'go to next' }} prev={{ 'aria-label': 'go to prev' }}>
        <CarouselNav>{index => <CarouselNavButton aria-label={`Carousel Nav Button ${index}`}/>}</CarouselNav>
      </CarouselNavContainer>
    </Carousel>);
};

export default Responsive;
```

## Top Navigation

Source: Fluent, adapted for CoreAI.

Top navigation places carousel controls at the header so users can see the title, page position, and navigation in one line. This story shows the default variant with previous and next buttons and dot pagination using CarouselNav inside CarouselNavContainer.

[Download source](/coreai-design-system/source/examples/components-carousel-carousel--top-navigation.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { typographyStyles } from "@coreai-ds/react-components";
import { Carousel } from "@coreai-ds/react-components";
import type { CarouselAnnouncerFunction } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { CarouselNavContainer } from "@coreai-ds/react-components";
import { CarouselNav } from "@coreai-ds/react-components";
import { CarouselNavButton } from "@coreai-ds/react-components";
import { CarouselViewport } from "@coreai-ds/react-components";
import { CarouselSlider } from "@coreai-ds/react-components";
import { CarouselCard } from "@coreai-ds/react-components";
import { Image } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";

const useClasses = makeStyles({
    bannerCard: {
        alignContent: 'center',
        borderRadius: tokens.borderRadiusXLarge,
        boxShadow: tokens.shadow16,
        height: '450px',
        textAlign: 'left',
        position: 'relative',
    },
    image: {
        borderRadius: 'inherit',
    },
    cardContainer: {
        display: 'flex',
        flexDirection: 'column',
        gap: tokens.spacingHorizontalS,
        position: 'absolute',
        left: '10%',
        top: '25%',
        borderRadius: tokens.borderRadiusLarge,
        boxShadow: tokens.shadow8,
        background: tokens.colorNeutralBackground1,
        padding: `${tokens.spacingHorizontalXXL} ${tokens.spacingVerticalXXXL}`,
        maxWidth: '270px',
        width: '50%',
    },
    title: {
        ...typographyStyles.title3,
    },
    subtext: {
        marginBottom: tokens.spacingVerticalM,
        ...typographyStyles.body1,
    },
    container: {
        display: 'grid',
        gridTemplateColumns: '1fr',
        gridTemplateRows: 'auto 1fr',
    },
    card: {
        minHeight: '100px',
    },
    carousel: {
        flex: 1,
        paddingBottom: tokens.spacingVerticalXL,
    },
    controls: {
        display: 'flex',
        flexDirection: 'column',
        gap: tokens.spacingVerticalSNudge,
        padding: `${tokens.spacingHorizontalMNudge} ${tokens.spacingVerticalMNudge}`,
    },
    field: {
        flex: 1,
        gridTemplateColumns: 'minmax(100px, max-content) 1fr',
    },
    dropdown: {
        maxWidth: 'max-content',
    },
    carouselHeader: {
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
        gap: tokens.spacingVerticalSNudge,
        marginBottom: tokens.spacingHorizontalL,
    },
    carouselHeaderTitle: {
        flex: '1',
        margin: '0',
        fontSize: tokens.fontSizeBase600,
        fontWeight: tokens.fontWeightSemibold,
    },
    carouselNavigation: { width: 'fit-content', alignSelf: 'center', margin: '0' },
    slider: {
        gap: tokens.spacingVerticalXXL,
        padding: `0 ${tokens.spacingVerticalXXL}`,
    },
});

const getAnnouncement: CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => {
    return `Carousel slide ${index + 1} of ${totalSlides}`;
};

const IMAGES = [
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/sea-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/bridge-full-img.jpg',
    'https://fabricweb.azureedge.net/fabric-website/assets/images/swatch-picker/park-full-img.jpg',
];

const BannerCard: React.FC<{
    children: React.ReactNode;
    imageSrc: string;
    index: number;
}> = props => {
    const { children, imageSrc, index } = props;
    const classes = useClasses();
    return (<CarouselCard autoSize className={classes.bannerCard} aria-label={`${index + 1} of ${IMAGES.length}`}>
      <Image fit="cover" src={imageSrc} role="presentation" className={classes.image}/>

      <div className={classes.cardContainer}>
        <div className={classes.title}>{children}</div>
        <div className={classes.subtext}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
          magna aliqua. Ut enim ad minim veniam.
        </div>
        <div>
          <Button appearance="primary">Call to action</Button>
        </div>
      </div>
    </CarouselCard>);
};

export const TopNavigation = (): React.ReactElement => {
    const classes = useClasses();
    return (<div className={classes.container}>
      <div className={classes.card}>
        <Carousel circular draggable announcement={getAnnouncement} className={classes.carousel}>
          <div className={classes.carouselHeader}>
            <Text as="h1" className={classes.carouselHeaderTitle}>
              Carousel Title
            </Text>
            <CarouselNavContainer next={{ 'aria-label': 'go to next' }} prev={{ 'aria-label': 'go to prev' }} className={classes.carouselNavigation}>
              <CarouselNav>{index => <CarouselNavButton aria-label={`Carousel Nav Button ${index}`}/>}</CarouselNav>
            </CarouselNavContainer>
          </div>
          <CarouselViewport>
            <CarouselSlider className={classes.slider}>
              {IMAGES.map((imageSrc, index) => (<BannerCard key={`image-${index}`} imageSrc={imageSrc} index={index}>
                  Card {index + 1}
                </BannerCard>))}
            </CarouselSlider>
          </CarouselViewport>
        </Carousel>
      </div>
    </div>);
};

export default TopNavigation;
```

## Resources

- [Figma component](https://www.figma.com/design/eXfSQrRg3poHRgmr0ivN5V/CoreAI-Figma-Library?node-id=322705-4097)
- [Fluent guidance](https://fluent2.microsoft.design/components/web/react/core/carousel/usage)