Introduction
Components
- Accordion
- Action Sheet
- Alert
- Audio Player
- Audio Recorder
- Audio Waveform
- Avatar
- Badge
- BottomSheet
- Button
- Camera
- Camera Preview
- Card
- Carousel
- Checkbox
- Collapsible
- Color Picker
- Combobox
- Date Picker
- File Picker
- Gallery
- Hello Wave
- Icon
- Image
- Input
- Input OTP
- Link
- MediaPicker
- Mode Toggle
- Onboarding
- ParallaxScrollView
- Picker
- Popover
- Progress
- Radio
- ScrollView
- SearchBar
- Separator
- Share
- Sheet
- Skeleton
- Spinner
- Switch
- Table
- Tabs
- Text
- Toast
- Toggle
- Video
- View
Charts
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
pnpm dlx bna-ui add carousel
Usage
import {
Carousel,
CarouselContent,
CarouselItem,
} from '@/components/ui/carousel';
<Carousel autoPlay showIndicators>
<CarouselItem>
<Text>Slide 1</Text>
</CarouselItem>
<CarouselItem>
<Text>Slide 2</Text>
</CarouselItem>
<CarouselItem>
<Text>Slide 3</Text>
</CarouselItem>
</Carousel>
Examples
Default
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
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
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useThemeColor } from '@/hooks/useThemeColor';
import React from 'react';
import { Dimensions } from 'react-native';
const { width: screenWidth } = Dimensions.get('window');
export function CarouselCustomWidth() {
const cardColor = useThemeColor({}, 'card');
const textColor = useThemeColor({}, '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
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
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useThemeColor } from '@/hooks/useThemeColor';
import { Calendar, MapPin, User } from 'lucide-react-native';
import React from 'react';
export function CarouselCards() {
const cardColor = useThemeColor({}, 'card');
const textColor = useThemeColor({}, 'text');
const primaryColor = useThemeColor({}, '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
import { Carousel, CarouselItem } from '@/components/ui/carousel';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useThemeColor } from '@/hooks/useThemeColor';
import { Minus, TrendingDown, TrendingUp } from 'lucide-react-native';
import React from 'react';
export function CarouselNoIndicators() {
const textColor = useThemeColor({}, '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
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
<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 comprehensive accessibility features:
- Screen Reader Support: Proper announcements for navigation controls and slide changes
- Keyboard Navigation: Full keyboard support for focus management and navigation
- Semantic Roles: Appropriate ARIA roles for carousel structure and components
- Accessible Indicators: Dot indicators with descriptive labels and touch targets
- Motion Preferences: Respects user's reduced motion preferences
- Focus Management: Logical focus order and visible focus indicators
- Alternative Text: Support for image descriptions and content labels
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