# Carousel

> A flexible carousel component with support for auto-play, indicators, arrows, and custom layouts.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/carousel
- Markdown: https://ui.ahmedbna.com/docs/components/carousel.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/carousel.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/carousel.json
- Install: `npx bna-ui add carousel`
- npm dependencies: `expo-blur`, `lucide-react-native`, `react-native-gesture-handler`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `globals`, `view`
- Preview recording: https://demo.ahmedbna.com/0097-carousel-demo.MP4

---

**Example:** A basic carousel with auto-play and indicators

```tsx
// components/demo/carousel/carousel-demo.tsx
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';

export function CarouselDemo() {
  return (
    <Carousel
      showIndicators={true}
      showArrows={true}
      autoPlay={true}
      loop={true}
    >
      {slides.map((slide) => (
        <CarouselItem
          key={slide.id}
          style={{
            minHeight: 200,
            justifyContent: 'center',
            alignItems: 'center',
          }}
        >
          <Text variant='subtitle'>{slide.title}</Text>
          <Text
            style={{
              marginTop: 8,
              opacity: 0.7,
            }}
          >
            {slide.content}
          </Text>
        </CarouselItem>
      ))}
    </Carousel>
  );
}

const slides = [
  {
    id: 1,
    title: 'Full Width Slide 1',
    content: 'This slide takes the full width of the container',
  },
  {
    id: 2,
    title: 'Full Width Slide 2',
    content: 'Perfect for hero sections and main content',
  },
  {
    id: 3,
    title: 'Full Width Slide 3',
    content: 'Uses paging for smooth navigation',
  },
  {
    id: 4,
    title: 'Full Width Slide 4',
    content: 'Default behavior - no spacing needed',
  },
];
```

## Installation

### CLI

```bash
npx bna-ui add carousel
```

### Manual

**1.** Install the following dependencies:

```bash
npx expo install expo-blur lucide-react-native react-native-gesture-handler
```

**2.** Copy and paste the following code into your project.

```tsx
// components/ui/carousel.tsx
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import { BlurView } from 'expo-blur';
import { ChevronLeft, ChevronRight } from 'lucide-react-native';
import React, {
  forwardRef,
  useCallback,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
} from 'react';
import {
  NativeScrollEvent,
  NativeSyntheticEvent,
  ScrollView,
  TouchableOpacity,
  useWindowDimensions,
  ViewStyle,
} from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

interface CarouselProps {
  children: React.ReactNode[];
  autoPlay?: boolean;
  autoPlayInterval?: number;
  showIndicators?: boolean;
  showArrows?: boolean;
  loop?: boolean;
  itemWidth?: number;
  spacing?: number;
  style?: ViewStyle;
  onIndexChange?: (index: number) => void;
}

interface CarouselItemProps {
  children: React.ReactNode;
  style?: ViewStyle[] | ViewStyle;
}

interface CarouselContentProps {
  children: React.ReactNode;
  style?: ViewStyle;
}

interface CarouselIndicatorsProps {
  total: number;
  current: number;
  onPress?: (index: number) => void;
  style?: ViewStyle;
}

interface CarouselArrowProps {
  direction: 'left' | 'right';
  onPress: () => void;
  disabled?: boolean;
  style?: ViewStyle;
}

// Define the ref interface
export interface CarouselRef {
  goToSlide: (index: number) => void;
  goToNext: () => void;
  goToPrevious: () => void;
  getCurrentIndex: () => number;
}

// Main Carousel Component
export const Carousel = forwardRef<CarouselRef, CarouselProps>(
  (
    {
      children,
      autoPlay = false,
      autoPlayInterval = 3000,
      showIndicators = true,
      showArrows = false,
      loop = false,
      itemWidth,
      spacing = 0,
      style,
      onIndexChange,
    },
    ref
  ) => {
    const { width: screenWidth } = useWindowDimensions();
    const scrollViewRef = useRef<ScrollView>(null);
    const [currentIndex, setCurrentIndex] = useState(0);
    const [containerWidth, setContainerWidth] = useState(screenWidth);
    const [isUserInteracting, setIsUserInteracting] = useState(false);

    // Use useRef to store timer ID and prevent stale closures
    const autoPlayTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
    const scrollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
    const currentIndexRef = useRef(currentIndex); // Keep ref in sync for auto play

    // Update ref when currentIndex changes
    useEffect(() => {
      currentIndexRef.current = currentIndex;
    }, [currentIndex]);

    // Calculate slide dimensions
    const slideWidth = itemWidth || containerWidth - spacing * 2;
    const snapToInterval = slideWidth + spacing;

    // Clear all timers
    const clearTimers = useCallback(() => {
      if (autoPlayTimerRef.current) {
        clearInterval(autoPlayTimerRef.current);
        autoPlayTimerRef.current = null;
      }
      if (scrollTimeoutRef.current) {
        clearTimeout(scrollTimeoutRef.current);
        scrollTimeoutRef.current = null;
      }
    }, []);

    // Scroll to current index
    const scrollToIndex = useCallback(
      (index: number, animated: boolean = true) => {
        if (scrollViewRef.current && index >= 0 && index < children.length) {
          const scrollX = index * snapToInterval;

          // Use requestAnimationFrame to ensure smooth scrolling
          requestAnimationFrame(() => {
            if (scrollViewRef.current) {
              scrollViewRef.current.scrollTo({
                x: scrollX,
                animated,
              });
            }
          });
        }
      },
      [snapToInterval, children.length]
    );

    // Navigation functions
    const goToSlide = useCallback(
      (index: number) => {
        if (index >= 0 && index < children.length && index !== currentIndex) {
          setCurrentIndex(index);
          setIsUserInteracting(true);
          scrollToIndex(index);

          // Clear auto play timeout to prevent conflicts
          if (scrollTimeoutRef.current) {
            clearTimeout(scrollTimeoutRef.current);
            scrollTimeoutRef.current = null;
          }
        }
      },
      [children.length, scrollToIndex, currentIndex]
    );

    const goToNext = useCallback(() => {
      const nextIndex = currentIndexRef.current + 1;
      const targetIndex =
        nextIndex < children.length
          ? nextIndex
          : loop
            ? 0
            : currentIndexRef.current;
      if (targetIndex !== currentIndexRef.current) {
        setCurrentIndex(targetIndex);
        setIsUserInteracting(true);
        scrollToIndex(targetIndex);

        // Clear auto play timeout to prevent conflicts
        if (scrollTimeoutRef.current) {
          clearTimeout(scrollTimeoutRef.current);
          scrollTimeoutRef.current = null;
        }
      }
    }, [children.length, loop, scrollToIndex]);

    const goToPrevious = useCallback(() => {
      const prevIndex = currentIndexRef.current - 1;
      const targetIndex =
        prevIndex >= 0
          ? prevIndex
          : loop
            ? children.length - 1
            : currentIndexRef.current;
      if (targetIndex !== currentIndexRef.current) {
        setCurrentIndex(targetIndex);
        setIsUserInteracting(true);
        scrollToIndex(targetIndex);

        // Clear auto play timeout to prevent conflicts
        if (scrollTimeoutRef.current) {
          clearTimeout(scrollTimeoutRef.current);
          scrollTimeoutRef.current = null;
        }
      }
    }, [loop, children.length, scrollToIndex]);

    // Expose methods through ref
    useImperativeHandle(
      ref,
      () => ({
        goToSlide,
        goToNext,
        goToPrevious,
        getCurrentIndex: () => currentIndex,
      }),
      [goToSlide, goToNext, goToPrevious, currentIndex]
    );

    // Start auto play - Fixed to actually scroll the view
    const startAutoPlay = useCallback(() => {
      if (!autoPlay || children.length <= 1 || isUserInteracting) return;

      clearTimers();

      autoPlayTimerRef.current = setInterval(() => {
        const nextIndex = currentIndexRef.current + 1;
        const targetIndex =
          nextIndex >= children.length
            ? loop
              ? 0
              : currentIndexRef.current
            : nextIndex;

        if (targetIndex !== currentIndexRef.current) {
          // Update state and scroll to new position
          setCurrentIndex(targetIndex);
          scrollToIndex(targetIndex, true);
        }
      }, autoPlayInterval);
    }, [
      autoPlay,
      autoPlayInterval,
      children.length,
      loop,
      isUserInteracting,
      clearTimers,
      scrollToIndex,
    ]);

    // Stop auto play
    const stopAutoPlay = useCallback(() => {
      clearTimers();
    }, [clearTimers]);

    // Handle auto play lifecycle
    useEffect(() => {
      if (autoPlay && !isUserInteracting) {
        startAutoPlay();
      } else {
        stopAutoPlay();
      }

      return stopAutoPlay;
    }, [autoPlay, isUserInteracting, startAutoPlay, stopAutoPlay]);

    // Handle index changes - notify parent component with debouncing
    useEffect(() => {
      // Use a small delay to prevent rapid-fire updates during navigation
      const timeoutId = setTimeout(() => {
        onIndexChange?.(currentIndex);
      }, 50);

      return () => clearTimeout(timeoutId);
    }, [currentIndex, onIndexChange]);

    // Handle scroll events - only update index from user scrolling
    const handleScroll = useCallback(
      (event: NativeSyntheticEvent<NativeScrollEvent>) => {
        // Only update index from scroll if user is manually scrolling
        if (isUserInteracting) {
          const scrollPosition = event.nativeEvent.contentOffset.x;
          const index = Math.round(scrollPosition / snapToInterval);

          if (index !== currentIndex && index >= 0 && index < children.length) {
            setCurrentIndex(index);
          }
        }
      },
      [currentIndex, snapToInterval, children.length, isUserInteracting]
    );

    // Handle momentum scroll end
    const handleMomentumScrollEnd = useCallback(
      (event: NativeSyntheticEvent<NativeScrollEvent>) => {
        const scrollPosition = event.nativeEvent.contentOffset.x;
        const index = Math.round(scrollPosition / snapToInterval);

        // Update index based on final scroll position
        if (index >= 0 && index < children.length && index !== currentIndex) {
          setCurrentIndex(index);
        }

        // Re-enable auto play after user interaction ends
        if (autoPlay) {
          scrollTimeoutRef.current = setTimeout(() => {
            setIsUserInteracting(false);
          }, 1000);
        }
      },
      [snapToInterval, children.length, autoPlay, currentIndex]
    );

    // Touch handlers
    const handleTouchStart = useCallback(() => {
      setIsUserInteracting(true);
    }, []);

    const handleTouchEnd = useCallback(() => {
      // Don't immediately re-enable auto play, let momentum scroll end handle it
    }, []);

    // Cleanup on unmount
    useEffect(() => {
      return () => {
        clearTimers();
      };
    }, [clearTimers]);

    const horizontalPan = Gesture.Pan()
      .onBegin(() => {
        // Optional: trigger when gesture starts
      })
      .onUpdate(() => {
        // Optional: you can track gesture updates here
      })
      .onEnd(() => {
        // Optional: trigger when gesture ends
      })
      .activeOffsetX([-10, 10]) // Allow horizontal pan
      .activeOffsetY([-1000, 1000]); // Block vertical gesture

    return (
      <View
        style={[
          {
            width: '100%',
            minWidth: itemWidth ? itemWidth + spacing * 2 : '100%',
          },
          style,
        ]}
        onLayout={(event) => {
          const { width } = event.nativeEvent.layout;
          // Ensure we have a valid width
          if (width > 0) {
            setContainerWidth(width);
          }
        }}
      >
        <View style={{ position: 'relative', overflow: 'hidden' }}>
          <GestureDetector gesture={horizontalPan}>
            <ScrollView
              ref={scrollViewRef}
              horizontal
              pagingEnabled={!itemWidth}
              snapToInterval={itemWidth ? snapToInterval : undefined}
              snapToAlignment={itemWidth ? 'start' : 'center'}
              decelerationRate={itemWidth ? 'fast' : 'normal'}
              showsHorizontalScrollIndicator={false}
              onScroll={handleScroll}
              onMomentumScrollEnd={handleMomentumScrollEnd}
              onTouchStart={handleTouchStart}
              onTouchEnd={handleTouchEnd}
              scrollEventThrottle={16}
              bounces={false}
              contentContainerStyle={
                itemWidth
                  ? {
                      paddingHorizontal: spacing,
                    }
                  : {
                      width: children.length * containerWidth,
                    }
              }
            >
              {children.map((child, index) => (
                <View
                  key={index}
                  style={{
                    width: slideWidth,
                    marginRight: itemWidth ? spacing : 0,
                  }}
                >
                  {child}
                </View>
              ))}
            </ScrollView>
          </GestureDetector>

          {showArrows && children.length > 1 && (
            <>
              <CarouselArrow
                direction='left'
                onPress={goToPrevious}
                disabled={!loop && currentIndex === 0}
                style={{
                  position: 'absolute',
                  left: 6,
                  top: '50%',
                  transform: [{ translateY: -12 }],
                  zIndex: 10,
                }}
              />
              <CarouselArrow
                direction='right'
                onPress={goToNext}
                disabled={!loop && currentIndex === children.length - 1}
                style={{
                  position: 'absolute',
                  right: 6,
                  top: '50%',
                  transform: [{ translateY: -12 }],
                  zIndex: 10,
                }}
              />
            </>
          )}
        </View>

        {showIndicators && children.length > 1 && (
          <CarouselIndicators
            total={children.length}
            current={currentIndex}
            onPress={goToSlide}
            style={{
              marginTop: 12,
              alignSelf: 'center',
            }}
          />
        )}
      </View>
    );
  }
);

// Carousel Content Component
export function CarouselContent({ children, style }: CarouselContentProps) {
  return <View style={style}>{children}</View>;
}

// Carousel Item Component - Auto height to fit content
export function CarouselItem({ children, style }: CarouselItemProps) {
  const backgroundColor = useColor('card');
  const borderColor = useColor('border');

  return (
    <View
      style={[
        {
          backgroundColor,
          borderRadius: BORDER_RADIUS,
          borderWidth: 1,
          borderColor,
          padding: 16,
          minHeight: 200, // Keep minimum height for consistency
        },
        style,
      ]}
    >
      {children}
    </View>
  );
}

// Carousel Indicators Component
export function CarouselIndicators({
  total,
  current,
  onPress,
  style,
}: CarouselIndicatorsProps) {
  const primaryColor = useColor('primary');
  const secondaryColor = useColor('secondary');

  return (
    <View
      style={[
        {
          flexDirection: 'row',
          justifyContent: 'center',
          alignItems: 'center',
          gap: 6,
        },
        style,
      ]}
    >
      {Array.from({ length: total }, (_, index) => (
        <TouchableOpacity
          key={index}
          onPress={() => onPress?.(index)}
          style={{
            width: 8,
            height: 8,
            borderRadius: 999,
            backgroundColor: index === current ? primaryColor : secondaryColor,
          }}
          hitSlop={{ top: 12, bottom: 12, left: 8, right: 8 }}
          accessibilityRole='button'
          accessibilityLabel={`Go to slide ${index + 1} of ${total}`}
          accessibilityState={{ selected: index === current }}
        />
      ))}
    </View>
  );
}

// Carousel Arrow Component
export function CarouselArrow({
  direction,
  onPress,
  disabled = false,
  style,
}: CarouselArrowProps) {
  const primaryColor = useColor('primary');

  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={disabled}
      style={[
        {
          width: 24,
          height: 24,
          borderRadius: 999,
          overflow: 'hidden',
          opacity: disabled ? 0.3 : 1,
        },
        style,
      ]}
      activeOpacity={0.7}
      accessibilityRole='button'
      accessibilityLabel={
        direction === 'left' ? 'Previous slide' : 'Next slide'
      }
      accessibilityState={{ disabled }}
    >
      <BlurView
        tint='systemChromeMaterial' // or "light"/"dark" depending on theme
        intensity={100}
        style={{
          flex: 1,
          borderRadius: 999,
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        {direction === 'left' ? (
          <ChevronLeft size={20} color={primaryColor} />
        ) : (
          <ChevronRight size={20} color={primaryColor} />
        )}
      </BlurView>
    </TouchableOpacity>
  );
}
```

**3.** Update the import paths to match your project setup.

## Usage

```tsx
import {
  Carousel,
  CarouselContent,
  CarouselItem,
} from '@/components/ui/carousel';
```

```tsx
<Carousel autoPlay showIndicators>
  <CarouselItem>
    <Text>Slide 1</Text>
  </CarouselItem>
  <CarouselItem>
    <Text>Slide 2</Text>
  </CarouselItem>
  <CarouselItem>
    <Text>Slide 3</Text>
  </CarouselItem>
</Carousel>
```

## Examples

#### Default

**Example:** A basic carousel with auto-play and indicators

```tsx
// components/demo/carousel/carousel-demo.tsx
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';

export function CarouselDemo() {
  return (
    <Carousel
      showIndicators={true}
      showArrows={true}
      autoPlay={true}
      loop={true}
    >
      {slides.map((slide) => (
        <CarouselItem
          key={slide.id}
          style={{
            minHeight: 200,
            justifyContent: 'center',
            alignItems: 'center',
          }}
        >
          <Text variant='subtitle'>{slide.title}</Text>
          <Text
            style={{
              marginTop: 8,
              opacity: 0.7,
            }}
          >
            {slide.content}
          </Text>
        </CarouselItem>
      ))}
    </Carousel>
  );
}

const slides = [
  {
    id: 1,
    title: 'Full Width Slide 1',
    content: 'This slide takes the full width of the container',
  },
  {
    id: 2,
    title: 'Full Width Slide 2',
    content: 'Perfect for hero sections and main content',
  },
  {
    id: 3,
    title: 'Full Width Slide 3',
    content: 'Uses paging for smooth navigation',
  },
  {
    id: 4,
    title: 'Full Width Slide 4',
    content: 'Default behavior - no spacing needed',
  },
];
```

#### With Arrows

**Example:** Carousel with navigation arrows and indicators

```tsx
// components/demo/carousel/carousel-arrows.tsx
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Icon } from '@/components/ui/icon';
import { Text } from '@/components/ui/text';
import { Award, Heart, Star, Zap } from 'lucide-react-native';

export function CarouselArrows() {
  const slides = [
    {
      icon: Heart,
      title: 'Loved by Users',
      description: 'Thousands of happy customers worldwide trust our products.',
      color: '#fee2e2',
      bg: '#FF6B6B',
    },
    {
      icon: Zap,
      title: 'Lightning Fast',
      description: 'Optimized for performance with smooth animations.',
      color: '#f3e8ff',
      bg: '#8b5cf6',
    },
    {
      icon: Star,
      title: 'Premium Quality',
      description: 'Built with the highest standards and attention to detail.',
      color: '#f59e0b',
      bg: '#fef3c7',
    },
    {
      icon: Award,
      title: 'Award Winning',
      description: 'Recognized for excellence in design and functionality.',
      color: '#d1fae5',
      bg: '#4ECDC4',
    },
  ];

  return (
    <Carousel showArrows showIndicators loop>
      {slides.map((slide, index) => {
        const name = slide.icon;
        return (
          <CarouselItem
            key={index}
            style={{
              alignItems: 'center',
              justifyContent: 'center',
              minHeight: 220,
              backgroundColor: slide.bg,
              padding: 20,
            }}
          >
            <Icon name={name} size={48} color={slide.color} />
            <Text
              variant='title'
              style={{
                color: slide.color,
                marginTop: 16,
                marginBottom: 8,
              }}
            >
              {slide.title}
            </Text>
            <Text
              style={{
                textAlign: 'center',
                color: slide.color,
                fontSize: 14,
                lineHeight: 20,
              }}
            >
              {slide.description}
            </Text>
          </CarouselItem>
        );
      })}
    </Carousel>
  );
}
```

#### Custom Item Width

**Example:** Carousel with custom item width and spacing

```tsx
// components/demo/carousel/carousel-custom-width.tsx
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import React from 'react';
import { Dimensions } from 'react-native';

const { width: screenWidth } = Dimensions.get('window');

export function CarouselCustomWidth() {
  const cardColor = useColor('card');
  const textColor = useColor('text');

  const products = [
    { id: 1, name: 'Wireless Headphones', price: '$99.99', rating: '4.8' },
    { id: 2, name: 'Smart Watch', price: '$199.99', rating: '4.9' },
    { id: 3, name: 'Bluetooth Speaker', price: '$79.99', rating: '4.7' },
    { id: 4, name: 'Phone Case', price: '$24.99', rating: '4.6' },
    { id: 5, name: 'Wireless Charger', price: '$39.99', rating: '4.8' },
  ];

  return (
    <View>
      <Carousel
        itemWidth={screenWidth * 0.7}
        spacing={16}
        showIndicators={false}
      >
        {products.map((product) => (
          <CarouselItem
            key={product.id}
            style={{
              backgroundColor: cardColor,
              minHeight: 160,
              padding: 20,
            }}
          >
            <View style={{ flex: 1, justifyContent: 'space-between' }}>
              <View>
                <Text
                  variant='title'
                  style={{
                    color: textColor,
                    fontSize: 18,
                    marginBottom: 8,
                  }}
                >
                  {product.name}
                </Text>
                <Text
                  style={{
                    color: textColor,
                    opacity: 0.7,
                    marginBottom: 12,
                  }}
                >
                  ⭐ {product.rating} rating
                </Text>
              </View>
              <Text
                variant='title'
                style={{
                  color: '#10b981',
                  fontSize: 20,
                  fontWeight: 'bold',
                }}
              >
                {product.price}
              </Text>
            </View>
          </CarouselItem>
        ))}
      </Carousel>

      <Text
        variant='caption'
        style={{
          marginTop: 16,
        }}
      >
        Swipe to see more products →
      </Text>
    </View>
  );
}
```

#### Image Carousel

**Example:** Image carousel with auto-play and loop

```tsx
// components/demo/carousel/carousel-images.tsx
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { BORDER_RADIUS } from '@/theme/globals';
import { Image } from 'expo-image';
import React from 'react';

export function CarouselImages() {
  const images = [
    {
      uri: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=300&fit=crop',
      title: 'Mountain Landscape',
      description: 'Breathtaking mountain views',
    },
    {
      uri: 'https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=400&h=300&fit=crop',
      title: 'Forest Path',
      description: 'Peaceful forest walking trail',
    },
    {
      uri: 'https://images.unsplash.com/photo-1501436513145-30f24e19fcc4?w=400&h=300&fit=crop',
      title: 'Ocean Sunset',
      description: 'Golden hour by the sea',
    },
    {
      uri: 'https://images.unsplash.com/photo-1500375592092-40eb2168fd21?w=400&h=300&fit=crop',
      title: 'Desert Dunes',
      description: 'Vast desert landscape',
    },
  ];

  return (
    <Carousel autoPlay autoPlayInterval={5000} showIndicators showArrows loop>
      {images.map((image, index) => (
        <CarouselItem key={index} style={{ padding: 0 }}>
          <View style={{ position: 'relative' }}>
            <Image
              source={{ uri: image.uri }}
              style={{
                width: '100%',
                height: 240,
                borderRadius: BORDER_RADIUS,
              }}
              contentFit='cover'
            />
            <View
              style={{
                position: 'absolute',
                bottom: 0,
                left: 0,
                right: 0,
                backgroundColor: 'rgba(0,0,0,0.6)',
                borderBottomLeftRadius: BORDER_RADIUS,
                borderBottomRightRadius: BORDER_RADIUS,
                padding: 16,
              }}
            >
              <Text
                variant='title'
                style={{
                  color: 'white',
                  fontSize: 18,
                  marginBottom: 4,
                }}
              >
                {image.title}
              </Text>
              <Text
                style={{
                  color: 'white',
                  opacity: 0.9,
                  fontSize: 14,
                }}
              >
                {image.description}
              </Text>
            </View>
          </View>
        </CarouselItem>
      ))}
    </Carousel>
  );
}
```

#### Card Carousel

**Example:** Card-based carousel with custom content

```tsx
// components/demo/carousel/carousel-cards.tsx
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { Calendar, MapPin, User } from 'lucide-react-native';
import React from 'react';

export function CarouselCards() {
  const cardColor = useColor('card');
  const textColor = useColor('text');
  const primaryColor = useColor('primary');

  const events = [
    {
      title: 'Tech Summit',
      location: 'New York, NY',
      date: 'Apr 8-10, 2024',
      attendees: 2100,
      category: 'Technology',
      color: '#10b981',
    },
    {
      title: 'Creative Workshop',
      location: 'Los Angeles, CA',
      date: 'May 22-23, 2024',
      attendees: 850,
      category: 'Creative',
      color: '#f59e0b',
    },
    {
      title: 'Design Conference 2024',
      location: 'San Francisco, CA',
      date: 'Mar 15-17, 2024',
      attendees: 1250,
      category: 'Design',
      color: '#3b82f6',
    },
  ];

  return (
    <Carousel showIndicators>
      {events.map((event, index) => (
        <CarouselItem key={index}>
          <View style={{ padding: 20 }}>
            {/* Category Badge */}
            <View
              style={{
                alignSelf: 'flex-start',
                backgroundColor: event.color,
                paddingHorizontal: 12,
                paddingVertical: 4,
                borderRadius: 12,
                marginBottom: 16,
              }}
            >
              <Text
                style={{
                  color: 'white',
                  fontSize: 12,
                  fontWeight: '600',
                }}
              >
                {event.category}
              </Text>
            </View>

            {/* Event Title */}
            <Text
              variant='title'
              style={{
                color: textColor,
                fontSize: 20,
                marginBottom: 16,
                lineHeight: 28,
              }}
            >
              {event.title}
            </Text>

            {/* Event Details */}
            <View style={{ gap: 12 }}>
              <View
                style={{
                  flexDirection: 'row',
                  alignItems: 'center',
                  gap: 8,
                }}
              >
                <MapPin size={16} color={primaryColor} />
                <Text style={{ color: textColor, flex: 1 }}>
                  {event.location}
                </Text>
              </View>

              <View
                style={{
                  flexDirection: 'row',
                  alignItems: 'center',
                  gap: 8,
                }}
              >
                <Calendar size={16} color={primaryColor} />
                <Text style={{ color: textColor, flex: 1 }}>{event.date}</Text>
              </View>

              <View
                style={{
                  flexDirection: 'row',
                  alignItems: 'center',
                  gap: 8,
                }}
              >
                <User size={16} color={primaryColor} />
                <Text style={{ color: textColor, flex: 1 }}>
                  {event.attendees.toLocaleString()} attendees
                </Text>
              </View>
            </View>

            {/* Action Button */}
            <View
              style={{
                backgroundColor: event.color,
                borderRadius: 8,
                paddingVertical: 12,
                paddingHorizontal: 16,
                marginTop: 20,
                alignItems: 'center',
              }}
            >
              <Text
                style={{
                  color: 'white',
                  fontWeight: '600',
                  fontSize: 16,
                }}
              >
                Learn More
              </Text>
            </View>
          </View>
        </CarouselItem>
      ))}
    </Carousel>
  );
}
```

#### No Indicators

**Example:** Carousel without indicators, arrows only

```tsx
// components/demo/carousel/carousel-no-indicators.tsx
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { Minus, TrendingDown, TrendingUp } from 'lucide-react-native';
import React from 'react';

export function CarouselNoIndicators() {
  const textColor = useColor('text');

  const stats = [
    {
      title: 'Revenue',
      value: '$24,500',
      change: '+12.5%',
      trend: 'up',
      color: '#10b981',
    },
    {
      title: 'Users',
      value: '8,432',
      change: '+5.2%',
      trend: 'up',
      color: '#3b82f6',
    },
    {
      title: 'Orders',
      value: '1,248',
      change: '-2.1%',
      trend: 'down',
      color: '#ef4444',
    },
    {
      title: 'Conversion',
      value: '3.8%',
      change: '0.0%',
      trend: 'neutral',
      color: '#6b7280',
    },
  ];

  const getTrendIcon = (trend: string) => {
    switch (trend) {
      case 'up':
        return TrendingUp;
      case 'down':
        return TrendingDown;
      default:
        return Minus;
    }
  };

  return (
    <Carousel showArrows showIndicators={false} loop>
      {stats.map((stat, index) => {
        const TrendIcon = getTrendIcon(stat.trend);
        return (
          <CarouselItem key={index}>
            <View
              style={{
                alignItems: 'center',
                justifyContent: 'center',
                minHeight: 180,
                padding: 24,
              }}
            >
              <Text
                style={{
                  color: textColor,
                  opacity: 0.7,
                  fontSize: 14,
                  marginBottom: 8,
                  textTransform: 'uppercase',
                  letterSpacing: 1,
                }}
              >
                {stat.title}
              </Text>

              <Text
                variant='title'
                style={{
                  color: textColor,
                  fontSize: 32,
                  fontWeight: 'bold',
                  marginBottom: 12,
                }}
              >
                {stat.value}
              </Text>

              <View
                style={{
                  flexDirection: 'row',
                  alignItems: 'center',
                  gap: 4,
                  backgroundColor: `${stat.color}15`,
                  paddingHorizontal: 12,
                  paddingVertical: 6,
                  borderRadius: 20,
                }}
              >
                <TrendIcon size={16} color={stat.color} />
                <Text
                  style={{
                    color: stat.color,
                    fontSize: 14,
                    fontWeight: '600',
                  }}
                >
                  {stat.change}
                </Text>
              </View>
            </View>
          </CarouselItem>
        );
      })}
    </Carousel>
  );
}
```

#### Manual Control

**Example:** Manually controlled carousel with external buttons

```tsx
// components/demo/carousel/carousel-manual.tsx
import { Button } from '@/components/ui/button';
import { Carousel, CarouselItem, CarouselRef } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { ChevronLeft, ChevronRight, RotateCcw } from 'lucide-react-native';
import React, { useRef, useState } from 'react';

export function CarouselManual() {
  const totalSlides = 4;
  const carouselRef = useRef<CarouselRef>(null);
  const [currentIndex, setCurrentIndex] = useState(0);

  const lessons = [
    {
      title: 'Introduction to React Native',
      progress: 0,
      duration: '15 min',
      bg: '#9c27b0',
      color: '#f3e5f5',
    },
    {
      title: 'Component Architecture',
      progress: 45,
      duration: '20 min',
      bg: '#ff5722',
      color: '#fff3e0',
    },
    {
      title: 'State Management',
      progress: 75,
      duration: '25 min',
      bg: '#00bcd4',
      color: '#e0f2f1',
    },
    {
      title: 'Navigation Patterns',
      progress: 100,
      duration: '18 min',
      bg: '#ff4081',
      color: '#fce4ec',
    },
  ];

  const handleGoToSlide = (index: number) => {
    if (carouselRef.current?.goToSlide) {
      carouselRef.current.goToSlide(index);
    }
  };

  const handleGoToPrevious = () => {
    if (currentIndex > 0) {
      if (carouselRef.current?.goToPrevious) {
        carouselRef.current.goToPrevious();
      }
    }
  };

  const handleGoToNext = () => {
    if (currentIndex < totalSlides - 1) {
      if (carouselRef.current?.goToNext) {
        carouselRef.current.goToNext();
      }
    }
  };

  const handleReset = () => {
    if (carouselRef.current?.goToSlide) {
      carouselRef.current.goToSlide(0);
    }
  };

  // Handle index changes from carousel (swipe gestures AND button presses)
  const handleIndexChange = (index: number) => {
    setCurrentIndex(index);
  };

  return (
    <View style={{ gap: 16 }}>
      {/* External Controls */}
      <View
        style={{
          paddingHorizontal: 4,
          flexDirection: 'row',
          alignItems: 'center',
          justifyContent: 'space-between',
        }}
      >
        <Button
          size='icon'
          variant='outline'
          icon={ChevronLeft}
          onPress={handleGoToPrevious}
          disabled={currentIndex === 0}
        />

        <View style={{ flexDirection: 'row', gap: 8 }}>
          {Array.from({ length: totalSlides }).map((_, index) => (
            <Button
              key={index}
              size='icon'
              variant={currentIndex === index ? 'default' : 'outline'}
              onPress={() => handleGoToSlide(index)}
              style={{ minWidth: 40 }}
            >
              {`${index + 1}`}
            </Button>
          ))}
        </View>

        <Button
          size='icon'
          variant='outline'
          icon={ChevronRight}
          onPress={handleGoToNext}
          disabled={currentIndex === totalSlides - 1}
        />
      </View>

      {/* Carousel */}
      <Carousel
        ref={carouselRef}
        showIndicators={false}
        onIndexChange={handleIndexChange}
      >
        {lessons.map((lesson, index) => (
          <CarouselItem
            key={index}
            style={{
              padding: 24,
              minHeight: 200,
              backgroundColor: lesson.bg,
              justifyContent: 'space-between',
            }}
          >
            <View>
              <Text
                variant='title'
                style={{
                  color: lesson.color,
                  fontSize: 20,
                  marginBottom: 12,
                }}
              >
                {lesson.title}
              </Text>

              <View
                style={{
                  flexDirection: 'row',
                  alignItems: 'center',
                  gap: 8,
                  marginBottom: 16,
                }}
              >
                <Text style={{ color: lesson.color, fontSize: 14 }}>
                  {lesson.duration}
                </Text>
                <View
                  style={{
                    width: 4,
                    height: 4,
                    borderRadius: 2,
                    backgroundColor: lesson.color,
                    opacity: 0.5,
                  }}
                />
                <Text style={{ color: lesson.color, fontSize: 14 }}>
                  {lesson.progress}% complete
                </Text>
              </View>
            </View>

            {/* Progress Bar */}
            <View>
              <View
                style={{
                  height: 16,
                  backgroundColor: 'white',
                  borderRadius: 999,
                  overflow: 'hidden',
                  marginBottom: 16,
                }}
              >
                <View
                  style={{
                    height: '100%',
                    width: `${lesson.progress}%`,
                    backgroundColor: 'black',
                    borderRadius: 999,
                  }}
                />
              </View>

              <Text
                style={{
                  color: lesson.color,
                  fontSize: 14,
                  textAlign: 'center',
                  opacity: 0.8,
                }}
              >
                Lesson {index + 1} of {totalSlides}
              </Text>
            </View>
          </CarouselItem>
        ))}
      </Carousel>

      {/* Reset Button */}
      <Button size='sm' variant='ghost' icon={RotateCcw} onPress={handleReset}>
        Reset to Start
      </Button>
    </View>
  );
}
```

## API Reference

### Carousel

The main carousel container component.

| Prop               | Type                      | Default | Description                                                |
| ------------------ | ------------------------- | ------- | ---------------------------------------------------------- |
| `children`         | `ReactNode[]`             | -       | Array of carousel items to display.                        |
| `autoPlay`         | `boolean`                 | `false` | Enable automatic slide progression.                        |
| `autoPlayInterval` | `number`                  | `3000`  | Interval between auto-play slides in milliseconds.         |
| `showIndicators`   | `boolean`                 | `true`  | Show dot indicators at the bottom.                         |
| `showArrows`       | `boolean`                 | `false` | Show navigation arrows on the sides.                       |
| `loop`             | `boolean`                 | `false` | Enable infinite loop mode.                                 |
| `itemWidth`        | `number`                  | -       | Custom width for carousel items (enables multi-item view). |
| `spacing`          | `number`                  | `0`     | Spacing between carousel items.                            |
| `style`            | `ViewStyle`               | -       | Additional styles for the carousel container.              |
| `onIndexChange`    | `(index: number) => void` | -       | Callback fired when the active slide changes.              |

### CarouselContent

A wrapper component for organizing carousel content.

| Prop       | Type        | Default | Description                                  |
| ---------- | ----------- | ------- | -------------------------------------------- |
| `children` | `ReactNode` | -       | The content to display inside the carousel.  |
| `style`    | `ViewStyle` | -       | Additional styles for the content container. |

### CarouselItem

Individual carousel slide component with default styling.

| Prop       | Type                       | Default | Description                               |
| ---------- | -------------------------- | ------- | ----------------------------------------- |
| `children` | `ReactNode`                | -       | The content to display in the slide.      |
| `style`    | `ViewStyle \| ViewStyle[]` | -       | Additional styles for the item container. |

### CarouselIndicators

Dot indicators component for showing current slide position.

| Prop      | Type                      | Default | Description                                     |
| --------- | ------------------------- | ------- | ----------------------------------------------- |
| `total`   | `number`                  | -       | Total number of slides.                         |
| `current` | `number`                  | -       | Current active slide index.                     |
| `onPress` | `(index: number) => void` | -       | Callback when an indicator is pressed.          |
| `style`   | `ViewStyle`               | -       | Additional styles for the indicators container. |

### CarouselArrow

Navigation arrow component for manual slide control.

| Prop        | Type                | Default | Description                                |
| ----------- | ------------------- | ------- | ------------------------------------------ |
| `direction` | `'left' \| 'right'` | -       | Arrow direction.                           |
| `onPress`   | `() => void`        | -       | Callback when the arrow is pressed.        |
| `disabled`  | `boolean`           | `false` | Whether the arrow is disabled.             |
| `style`     | `ViewStyle`         | -       | Additional styles for the arrow container. |

### Custom Styling

```tsx
<Carousel
  style={{
    backgroundColor: '#f8f9fa',
    borderRadius: 12,
    overflow: 'hidden',
  }}
  showIndicators
  showArrows
>
  <CarouselItem
    style={{
      backgroundColor: 'white',
      margin: 8,
      borderRadius: 8,
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 2 },
      shadowOpacity: 0.1,
      shadowRadius: 4,
      elevation: 3,
    }}
  >
    {/* Your content */}
  </CarouselItem>
</Carousel>
```

## Features

- **Auto-play**: Automatic slide progression with customizable intervals
- **Touch Gestures**: Swipe to navigate between slides using react-native-gesture-handler
- **Indicators**: Dot indicators showing current position with tap-to-navigate
- **Navigation Arrows**: Optional left/right arrow controls with blur effects
- **Loop Mode**: Infinite scrolling capability for seamless navigation
- **Custom Layouts**: Support for custom item widths and spacing for multi-item views
- **Responsive**: Adapts to container width automatically
- **Smooth Animations**: Hardware-accelerated scrolling with momentum and spring physics
- **Accessibility**: Built-in accessibility support with proper ARIA labels

## Accessibility

The Carousel component includes accessibility features:

- Dot indicators expose `accessibilityLabel` (e.g. "Go to slide 2 of 5") and `accessibilityState={{ selected }}`
- Arrow buttons are labeled "Previous slide"/"Next slide" and report their disabled state

## Performance Tips

- Use `itemWidth` prop for multi-item carousels to optimize rendering
- Implement lazy loading for image carousels with many slides
- Consider disabling auto-play for carousels with heavy content
- Use `onIndexChange` callback to preload adjacent slides for smoother navigation
