# How to Use the Astryx Carousel Component for Image/Content Rotation

> Master the Astryx Carousel component for dynamic image and content rotation. Learn to implement fade-edge masks, navigation, and programmatic control for seamless user experiences.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-08-06

---

**The Astryx Carousel component is a flexible, accessible horizontal-scroll container that automatically adds fade-edge masks, navigation buttons, and RTL support while exposing imperative methods like `scrollNext()` and `scrollTo()` for programmatic control.**

The Astryx Carousel component in the `facebook/astryx` repository provides a production-ready solution for displaying horizontally scrollable content rows without consuming full viewport width. Built with StyleX for theme-aware styling and comprehensive accessibility features, this component handles overflow indication, navigation controls, and keyboard interactions out of the box.

## Importing the Carousel Component

The Carousel is exported from the core package entry point defined in [`packages/core/src/Carousel/index.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/Carousel/index.ts). Import the component and its TypeScript definitions directly from the package:

```tsx
import {Carousel} from '@astryxdesign/core';
import type {CarouselHandle} from '@astryxdesign/core';

```

## Basic Implementation

At its core, the Carousel wraps children in a scrollable container. In [`packages/core/src/Carousel/Carousel.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Carousel/Carousel.tsx), the component renders a `div` with `overflow-x: auto` (referenced internally as `styles.scroller`) that enables native horizontal scrolling:

```tsx
import {Carousel} from '@astryxdesign/core';
import {Thumbnail} from '@astryxdesign/core';

export function ImageCarousel() {
  return (
    <Carousel gap={1} aria-label="Featured photos">
      <Thumbnail src="/images/a.jpg" alt="Photo A" />
      <Thumbnail src="/images/b.jpg" alt="Photo B" />
      <Thumbnail src="/images/c.jpg" alt="Photo C" />
    </Carousel>
  );
}

```

The `gap` prop applies spacing between items using StyleX tokens, while `aria-label` defines the accessible name for the carousel region.

## Core Architecture and Scroll Mechanics

The component's architecture centers on the `useScrollOverflow` hook, which monitors the scrollable container's dimensions to drive UI state. This hook determines when content overflows its bounds, triggering the visibility of fade-edge masks and navigation buttons.

### Overflow Indicators and Fade Masks

When content exceeds the visible area, the Carousel applies CSS-based masks to indicate scrollability. The implementation in [`Carousel.tsx`](https://github.com/facebook/astryx/blob/main/Carousel.tsx) utilizes three StyleX style objects:

- **`styles.fadeStart`**: Gradient mask at the left edge
- **`styles.fadeEnd`**: Gradient mask at the right edge  
- **`styles.fadeBoth`**: Masks applied to both edges when scrolling is possible in either direction

These masks provide visual affordances without requiring additional DOM elements.

### Input Handling and Scroll Behavior

The Carousel supports multiple input methods through event handlers defined in the source:

- **Native trackpad gestures** leverage the browser's standard horizontal scroll behavior on the `styles.scroller` element.
- **Mouse wheel support** maps vertical wheel deltas to horizontal scrolling via the `handleWheel` callback, enabling horizontal navigation when users hold Shift while scrolling.

Navigation buttons trigger the `scrollBy(direction)` function, which respects RTL layouts and calculates scroll distance based on item dimensions and the `gap` prop.

## Navigation Controls and Looping

By default, the Carousel renders Previous and Next buttons that appear only when scrolling is possible in the respective direction, as determined by `canScrollPrev()` and `canScrollNext()` logic within the `useScrollOverflow` hook.

Enable infinite looping by setting the `hasLoop` prop to `true`. When activated, the `scrollBy` function wraps around: clicking **Next** at the end scrolls to the first item, and **Previous** at the start jumps to the last item, keeping both buttons persistently visible.

```tsx
import {Carousel} from '@astryxdesign/core';
import {Card} from '@astryxdesign/core';

export function ProductCarousel() {
  return (
    <Carousel
      gap={2}
      hasSnap
      hasLoop
      padding={2}
      aria-label="Featured products"
    >
      <Card padding={4}>Product 1</Card>
      <Card padding={4}>Product 2</Card>
      <Card padding={4}>Product 3</Card>
      <Card padding={4}>Product 4</Card>
    </Carousel>
  );
}

```

The `hasSnap` prop enables CSS scroll-snap for precise item alignment during scrolling.

## Imperative Control with Handle Refs

For programmatic navigation, the Carousel exposes an imperative handle via the `handleRef` prop. This ref provides a `CarouselHandle` interface with five methods defined in [`packages/core/src/Carousel/Carousel.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Carousel/Carousel.tsx):

- **`scrollNext()`**: Advances to the next item (respects `hasLoop`)
- **`scrollPrev()`**: Returns to the previous item
- **`scrollTo(index)`**: Jumps to a specific zero-based index
- **`canScrollNext()`**: Returns boolean indicating if next scroll is possible
- **`canScrollPrev()`**: Returns boolean indicating if previous scroll is possible

```tsx
import {useRef} from 'react';
import {Carousel, type CarouselHandle} from '@astryxdesign/core';

export function ControlledCarousel() {
  const carouselRef = useRef<CarouselHandle>(null);

  const goToThird = () => {
    carouselRef.current?.scrollTo(2); // zero-based index
  };

  return (
    <>
      <button onClick={goToThird}>Go to slide 3</button>
      <Carousel handleRef={carouselRef} hasButtons={false} aria-label="Gallery">
        {/* slides */}
      </Carousel>
    </>
  );
}

```

This pattern is useful for syncing external controls or deep-linking to specific slides.

## Accessibility Implementation

The Carousel implements the WAI-ARIA Carousel pattern with semantic attributes set in [`packages/core/src/Carousel/Carousel.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Carousel/Carousel.tsx):

- The root element carries **`role="region"`**, **`aria-roledescription="carousel"`**, and an `aria-label` (defaulting to "Carousel")
- Each child slide wrapper receives **`role="group"`**, **`aria-roledescription="slide"`**, and an accessible label like "Slide 2 of 5" generated via the translator utility
- Navigation buttons maintain visible focus indicators and respect reduced-motion preferences

These attributes ensure screen reader users understand the component's structure and their current position within the rotation.

## Configuration Options

Control the Carousel's appearance and behavior through these props:

- **`gap`**: Space between items (number, theme token multiplier)
- **`padding`**: Internal container padding (number, theme token multiplier)
- **`hasSnap`**: Boolean enabling CSS scroll-snap alignment
- **`hasLoop`**: Boolean enabling infinite scroll wrapping
- **`hasButtons`**: Boolean toggling Prev/Next navigation buttons (default: true)
- **`hasEdgeFade`**: Boolean toggling overflow gradient masks (default: true)
- **`handleRef`**: React ref exposing `CarouselHandle` for imperative control

To create a minimal carousel without auxiliary UI elements:

```tsx
import {Carousel} from '@astryxdesign/core';

export function SimpleCarousel() {
  return (
    <Carousel hasButtons={false} hasEdgeFade={false} aria-label="Team members">
      {/* slides */}
    </Carousel>
  );
}

```

## Summary

- The **Astryx Carousel component** provides a horizontal-scroll container with automatic overflow detection and visual fade masks defined in [`packages/core/src/Carousel/Carousel.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Carousel/Carousel.tsx).
- **Navigation** supports trackpad gestures, Shift+mouse wheel, and optional Prev/Next buttons with RTL adaptation and `hasLoop` wrapping.
- **Imperative control** is available via `handleRef` exposing `scrollNext()`, `scrollPrev()`, `scrollTo(index)`, and state-checking methods.
- **Accessibility** includes `role="region"`, `aria-roledescription="carousel"`, and slide-specific labels for screen reader compatibility.
- **StyleX** drives all styling, making the component theme-aware and token-driven.

## Frequently Asked Questions

### How do I programmatically navigate to a specific slide in the Astryx Carousel?

Use the imperative `handleRef` API. Create a ref with `useRef<CarouselHandle>(null)`, pass it to the `handleRef` prop, and call `ref.current?.scrollTo(index)` where index is zero-based. This method is defined in [`packages/core/src/Carousel/Carousel.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Carousel/Carousel.tsx) and respects the `hasLoop` configuration.

### Does the Carousel component support right-to-left (RTL) languages?

Yes, the Carousel automatically adapts to RTL layouts. The `scrollBy` function and navigation button chevrons adjust their direction based on document direction. The underlying scroll behavior and fade masks (styles.fadeStart/styles.fadeEnd) swap positions to match the reading direction.

### How do I disable the navigation buttons and edge fade effects?

Set the `hasButtons` and `hasEdgeFade` props to `false`. This removes the Prev/Next button overlay and the CSS gradient masks (`styles.fadeBoth`) while preserving the horizontal scroll functionality and accessibility attributes.

### What accessibility features are built into the Astryx Carousel?

According to the source code in [`packages/core/src/Carousel/Carousel.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Carousel/Carousel.tsx), the root element has `role="region"` and `aria-roledescription="carousel"`, while each slide receives `role="group"` and `aria-roledescription="slide"` with generated labels indicating position (e.g., "Slide 2 of 5"). The component also respects reduced-motion preferences and provides keyboard-navigable controls.