Carousel

Updated: Sep 24, 2026
Use Carousel for a horizontal sequence that centers the focused item. Each child becomes one item in a focusable slot. Carousel owns scrolling, centering, edge spacing for the first and last items, horizontal fades, and the optional dot or text position indicator. This ownership keeps focus and visible position synchronized.
Items can contain cards, containers, media, or custom compositions. Prefer Pager when every destination occupies a whole page.

Usage

import {
  Card,
  Carousel,
  PaginationMode,
  TextStyle,
  TextView,
} from '@wearables-ui-toolkit/mrbd';

export function ConditionsCarousel({ days }: { days: Forecast[] }) {
  return (
    <Carousel
      ariaLabel="Conditions"
      paginationMode={PaginationMode.DOTS}
      onItemCentered={index => reportVisibleDay(days[index])}
    >
      {days.map(day => (
        <Card key={day.id} ariaLabel={day.label}>
          <TextView textStyle={TextStyle.BODY2_EMPHASIZED}>{day.label}</TextView>
          <TextView textStyle={TextStyle.BODY2}>{day.summary}</TextView>
        </Card>
      ))}
    </Carousel>
  );
}
To control the carousel from another part of the screen, keep a ref to its imperative handle:
import { useRef } from 'react';
import { Button, Carousel } from '@wearables-ui-toolkit/mrbd';
import type { CarouselHandle } from '@wearables-ui-toolkit/mrbd';

export function CaptureStrip({ captures }: { captures: Capture[] }) {
  const carouselRef = useRef<CarouselHandle>(null);

  return (
    <>
      <Carousel ref={carouselRef} ariaLabel="Captures">
        {captures.map(capture => (
          <CaptureTile key={capture.id} capture={capture} />
        ))}
      </Carousel>
      <Button
        title="Latest"
        // Centers, focuses, and reports the item.
        onActivate={() => carouselRef.current?.scrollToPosition(captures.length - 1)}
      />
    </>
  );
}
Consider overlaying the indicator when the strip should remain visually compact:
import {
  Carousel,
  CarouselIndicatorPlacement,
  CarouselVerticalAlignment,
  PaginationMode,
} from '@wearables-ui-toolkit/mrbd';

<Carousel
  ariaLabel="Gallery"
  indicatorPlacement={CarouselIndicatorPlacement.BOTTOM_OVERLAY}
  paginationMode={PaginationMode.DOTS}
  verticalAlignment={CarouselVerticalAlignment.BOTTOM}
>
  {tiles}
</Carousel>;
Carousel is also available from the component subpath:
import { Carousel } from '@wearables-ui-toolkit/foundation/components/Carousel';

Props

The complete prop type is CarouselProps. The prop set is closed. Unlike most toolkit components, Carousel does not spread additional DOM attributes onto its root, so callers must use only the props listed here.
PropTypeDefaultDescription
children
ReactNode
—
Children converted into items in source order.
itemGap
number
the standard item gap
Gap between items in pixels. Each interior item receives half on either side.
onItemCentered
(index: number) => void
—
Called with the zero-based index when an item becomes centered.
showPagination
boolean
true
Controls whether the position indicator can appear.
paginationMode
PaginationMode
PaginationMode.TEXT
Renders the indicator as text or dots.
verticalAlignment
CarouselVerticalAlignment
CarouselVerticalAlignment.CENTER
Vertical item alignment within the strip.
indicatorPlacement
CarouselIndicatorPlacement
CarouselIndicatorPlacement.UNDER
Places the indicator below the strip or over its bottom edge.
overlayDistance
number
the standard overlay distance
Indicator distance in pixels from the bottom. Applies only to BOTTOM_OVERLAY.
initialIndex
number
0
Item centered on mount. Changes after mount are ignored.
fadingEdgeLength
number
the standard horizontal fade length
Length in pixels of each horizontal fade.
progressIndicatorCustomizationOverride
(index: number, numItems: number) => ProgressIndicatorCustomization
—
Per-render hook that replaces the indicator’s current position, total, and visibility.
className
string
''
Class added to the carousel root.
style
CSSProperties
—
Inline styles applied to the carousel root.
ariaLabel
string
—
Accessible name for the carousel region. When omitted, the component uses a generic fallback.
tabIndex
number
0
Tab index of the scrolling strip.

Types and enums

CarouselVerticalAlignment
  • CENTER — centers items vertically within the strip
  • BOTTOM — aligns items to the bottom of the strip
CarouselIndicatorPlacement
  • UNDER — places the indicator below the strip and reserves space for it
  • BOTTOM_OVERLAY — overlays the indicator on the bottom of the strip
ProgressIndicatorCustomization
  • currentIndex: number — current dot or page, replacing the centered item index
  • totalItems: number — dot or page count, replacing the item count
  • isVisible: boolean — hides the indicator when false, even when it would otherwise appear
PaginationMode is defined by PaginationIndicator.

Foundation pagination presentation

The foundation package exposes the indicator slot used by its lower-level Carousel. CarouselPaginationPresentationProvider accepts children and a presentation component. That component receives CarouselPaginationPresentationProps:
PropTypeDescription
mode
PaginationMode
Requested text or dot presentation.
pageCount
number
Total represented positions after any progress customization.
currentPage
number
Zero-based represented position after any progress customization.
The presentation prop has the exported CarouselPaginationPresentation component type. It must render the complete indicator and provide any accessibility semantics its markup requires.
The Meta Ray-Ban Display Carousel already supplies PaginationIndicator, so applications normally use paginationMode and progressIndicatorCustomizationOverride instead. Use the provider only when rendering the lower-level foundation Carousel with another presentation.

Imperative handle

The ref exposes a CarouselHandle rather than a DOM node:
  • scrollToPosition(index: number): void — centers, focuses, and reports the item at index. Out-of-range indices are ignored and do not produce a report.
  • getRootElement(): HTMLDivElement | null — returns the root DOM element, or null before mount.

Behavior

Centering. When an item receives focus, Carousel smoothly scrolls it to the center. Direct scrolling also snaps items to the center. The first and last items receive a measured edge offset equal to half the difference between the strip width and the item’s width, which lets those items reach the center. Size items with enough room at both horizontal edges for these offsets. Carousel observes the strip and every item for resize, then recalculates offsets and fades as the responsive layout changes.
Focus. Each item slot is focusable. When a slot contains focusable content, Carousel forwards focus to its first focusable descendant instead of leaving focus on the slot. When focus reaches the strip, the component moves it to the currently centered item, recenters the strip, and reports the item through onItemCentered.
Keyboard.ArrowLeft and ArrowRight move between items without wrapping. Carousel consumes a key that changes the selection. At either boundary, it leaves the key unhandled so an outer directional navigation container, such as a Pager, can respond. The strip does not show a scrollbar, so focus, the arrow keys, and scrollToPosition provide its movement affordances.
Fades. Fixed-width overlays form the left and right fades. Their opacity increases linearly with distance from the corresponding end and reaches full strength after that distance exceeds the fade length. The fade at an end is transparent while the strip is scrolled fully to that end. Each fade uses a gradient to the window background semantic token instead of a flat fill, so it works consistently across surfaces. fadingEdgeLength defaults to a shorter fade than ScrollView.
Indicator. Text mode shows the indicator for one or more items. Dot mode shows it for two or more items, based on the total currently rendered by the indicator. With BOTTOM_OVERLAY, overlayDistance sets the indicator’s bottom padding; UNDER ignores it. When provided, progressIndicatorCustomizationOverride receives the active index and item count. Its return value replaces the current position, total, and visibility used by the indicator.
Changing items.Carousel resolves items from children on every render, so keyed children can be added or removed. If a shorter list removes the centered index, the component clamps to the last item and recenters it. initialIndex applies only during mount and only when it identifies an item other than the first.

Accessibility

  • The root has a region role and a carousel role description. Give it an accessible name through ariaLabel; otherwise, the component uses a generic fallback name.
  • The strip has a list role, and each item slot has a listitem role labelled with its position in the sequence. Item content owns its accessible name and actions.
  • The strip owns the tab stop, and arrow keys move among items. Keep tabIndex at its default unless the surrounding application explicitly manages tab order.
  • The component marks fades aria-hidden so assistive technology does not announce decorative content.
  • Carousel renders every item and does not virtualize the sequence, so every item remains available to assistive technology.
  • Size items responsively and leave space for focus scaling at both horizontal edges.
  • Let Carousel own scrolling, centering, and indicator state. Observe centered items through onItemCentered.
  • Keep onItemCentered inexpensive and idempotent because one movement can report the same index more than once.
  • Keep the indicator visually close to its items so its relationship remains clear.
  • Use progressIndicatorCustomizationOverride when the indicator represents a grouping other than the raw item list, such as a paged set.

Avoid

  • Avoid a large empty area between the items and indicator because it weakens their visual relationship.
  • Do not nest another horizontal scrolling region inside an item or put a carousel inside a horizontal ScrollView. One scroller must own each axis so directional navigation and scrolling remain predictable.
  • Use scrollToPosition instead of managing the scroll position directly. This keeps position, focus, and centered-item reporting synchronized.
  • Do not pass arbitrary DOM attributes expecting them on the root. The public prop set is closed.
  • Avoid using a carousel for full-page destinations; Carousel is intended for items within a page.