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 { Progress } from '@/components/ui/progress';
import React from 'react';
export function ProgressDemo() {
return <Progress value={65} />;
}
Installation
pnpm dlx bna-ui add progress
Usage
import { Progress } from '@/components/ui/progress';
<Progress value={65} />
Examples
Default
import { Progress } from '@/components/ui/progress';
import React from 'react';
export function ProgressDemo() {
return <Progress value={65} />;
}
Interactive
import { Progress } from '@/components/ui/progress';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';
export function ProgressInteractive() {
const [value, setValue] = useState(45);
const [isSeking, setIsSeeking] = useState(false);
return (
<View style={{ gap: 12 }}>
<Text variant='body' style={{ color: '#666' }}>
{isSeking ? 'Seeking...' : `Progress: ${Math.round(value)}%`}
</Text>
<Progress
value={value}
interactive
height={18}
onValueChange={setValue}
onSeekStart={() => setIsSeeking(true)}
onSeekEnd={() => setIsSeeking(false)}
/>
<Text variant='caption' style={{ color: '#999' }}>
Tap or drag to adjust the progress
</Text>
</View>
);
}
Custom Heights
import { Progress } from '@/components/ui/progress';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';
export function ProgressHeights() {
return (
<View style={{ gap: 16 }}>
<View style={{ gap: 6 }}>
<Text variant='caption'>Small (2px)</Text>
<Progress value={75} height={2} />
</View>
<View style={{ gap: 6 }}>
<Text variant='caption'>Default (4px)</Text>
<Progress value={60} />
</View>
<View style={{ gap: 6 }}>
<Text variant='caption'>Medium (8px)</Text>
<Progress value={45} height={8} />
</View>
<View style={{ gap: 6 }}>
<Text variant='caption'>Large (12px)</Text>
<Progress value={30} height={12} />
</View>
<View style={{ gap: 6 }}>
<Text variant='caption'>Extra Large (20px)</Text>
<Progress value={85} height={20} />
</View>
</View>
);
}
With Labels
import { Progress } from '@/components/ui/progress';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';
export function ProgressLabels() {
const tasks = [
{ label: 'Installing dependencies', progress: 100 },
{ label: 'Building application', progress: 75 },
{ label: 'Running tests', progress: 45 },
{ label: 'Deploying to production', progress: 0 },
];
return (
<View style={{ gap: 20 }}>
{tasks.map((task, index) => (
<View key={index} style={{ gap: 8 }}>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Text variant='body' style={{ fontWeight: '500' }}>
{task.label}
</Text>
<Text variant='caption' style={{ color: '#666' }}>
{task.progress}%
</Text>
</View>
<Progress value={task.progress} height={6} />
</View>
))}
<View style={{ gap: 8, marginTop: 12 }}>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Text variant='title' style={{ fontWeight: '600' }}>
Overall Progress
</Text>
<Text variant='body' style={{ fontWeight: '500' }}>
55%
</Text>
</View>
<Progress value={55} height={10} />
<Text variant='caption' style={{ color: '#666' }}>
2 of 4 tasks completed
</Text>
</View>
</View>
);
}
Animated
import { Progress } from '@/components/ui/progress';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useEffect, useState } from 'react';
export function ProgressAnimated() {
const [progress1, setProgress1] = useState(0);
const [progress2, setProgress2] = useState(0);
const [progress3, setProgress3] = useState(0);
useEffect(() => {
// Animate first progress bar
const timer1 = setTimeout(() => setProgress1(75), 500);
// Animate second progress bar
const timer2 = setTimeout(() => setProgress2(60), 1000);
// Animate third progress bar
const timer3 = setTimeout(() => setProgress3(85), 1500);
return () => {
clearTimeout(timer1);
clearTimeout(timer2);
clearTimeout(timer3);
};
}, []);
const [cycleProgress, setCycleProgress] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCycleProgress((prev) => {
const newValue = prev + 10;
return newValue > 100 ? 0 : newValue;
});
}, 300);
return () => clearInterval(interval);
}, []);
return (
<View style={{ gap: 20 }}>
<View style={{ gap: 12 }}>
<Text variant='title'>Staggered Animation</Text>
<View style={{ gap: 8 }}>
<Text variant='caption'>File Upload: {progress1}%</Text>
<Progress value={progress1} height={6} />
</View>
<View style={{ gap: 8 }}>
<Text variant='caption'>Processing: {progress2}%</Text>
<Progress value={progress2} height={6} />
</View>
<View style={{ gap: 8 }}>
<Text variant='caption'>Optimization: {progress3}%</Text>
<Progress value={progress3} height={6} />
</View>
</View>
<View style={{ gap: 12 }}>
<Text variant='title'>Continuous Animation</Text>
<View style={{ gap: 8 }}>
<Text variant='caption'>Loading: {cycleProgress}%</Text>
<Progress value={cycleProgress} height={8} />
</View>
</View>
</View>
);
}
Media Player Style
import { Progress } from '@/components/ui/progress';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';
import { TouchableOpacity } from 'react-native';
export function ProgressMedia() {
const [progress, setProgress] = useState(35);
const [isPlaying, setIsPlaying] = useState(false);
const [volume, setVolume] = useState(75);
const formatTime = (percent: number) => {
const totalSeconds = Math.floor((percent / 100) * 180); // 3 minute song
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
};
return (
<View style={{ gap: 20 }}>
{/* Media Player */}
<View
style={{
backgroundColor: '#1a1a1a',
borderRadius: 12,
padding: 16,
gap: 12,
}}
>
<View style={{ gap: 8 }}>
<Text variant='body' style={{ color: '#fff', fontWeight: '600' }}>
Song Title
</Text>
<Text variant='caption' style={{ color: '#999' }}>
Artist Name
</Text>
</View>
<View style={{ gap: 8 }}>
<Progress
value={progress}
interactive
height={4}
onValueChange={setProgress}
style={{ backgroundColor: '#333' }}
/>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
<Text variant='caption' style={{ color: '#999' }}>
{formatTime(progress)}
</Text>
<Text variant='caption' style={{ color: '#999' }}>
3:00
</Text>
</View>
</View>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 20,
}}
>
<TouchableOpacity
style={{
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#007AFF',
justifyContent: 'center',
alignItems: 'center',
}}
onPress={() => setIsPlaying(!isPlaying)}
>
<Text style={{ color: '#fff', fontSize: 20 }}>
{isPlaying ? '⏸️' : '▶️'}
</Text>
</TouchableOpacity>
</View>
</View>
{/* Volume Control */}
<View
style={{
backgroundColor: '#f8f9fa',
borderRadius: 8,
padding: 12,
gap: 8,
}}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
gap: 12,
}}
>
<Text>🔊</Text>
<View style={{ flex: 1 }}>
<Progress
value={volume}
interactive
height={6}
onValueChange={setVolume}
/>
</View>
<Text variant='caption' style={{ color: '#666' }}>
{Math.round(volume)}%
</Text>
</View>
</View>
</View>
);
}
Step Progress
import { Progress } from '@/components/ui/progress';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';
import { TouchableOpacity } from 'react-native';
export function ProgressSteps() {
const [currentStep, setCurrentStep] = useState(2);
const steps = ['Account Setup', 'Personal Info', 'Verification', 'Complete'];
const progress = (currentStep / (steps.length - 1)) * 100;
return (
<View style={{ gap: 20 }}>
{/* Step Progress */}
<View style={{ gap: 16 }}>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Text variant='title'>Setup Progress</Text>
<Text variant='caption' style={{ color: '#666' }}>
Step {currentStep + 1} of {steps.length}
</Text>
</View>
<Progress value={progress} height={8} />
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 8,
}}
>
{steps.map((step, index) => (
<View
key={index}
style={{
alignItems: 'center',
flex: 1,
}}
>
<View
style={{
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: index <= currentStep ? '#007AFF' : '#e5e7eb',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 8,
}}
>
<Text
variant='caption'
style={{
color: index <= currentStep ? '#fff' : '#666',
fontWeight: '600',
}}
>
{index < currentStep ? '✓' : index + 1}
</Text>
</View>
<Text
variant='caption'
style={{
color: index <= currentStep ? '#000' : '#999',
fontWeight: index === currentStep ? '600' : '400',
textAlign: 'center',
}}
>
{step}
</Text>
</View>
))}
</View>
</View>
{/* Controls */}
<View
style={{
flexDirection: 'row',
gap: 12,
justifyContent: 'center',
}}
>
<TouchableOpacity
style={{
paddingHorizontal: 16,
paddingVertical: 8,
backgroundColor: currentStep > 0 ? '#007AFF' : '#e5e7eb',
borderRadius: 6,
}}
onPress={() => setCurrentStep(Math.max(0, currentStep - 1))}
disabled={currentStep === 0}
>
<Text
style={{
color: currentStep > 0 ? '#fff' : '#999',
fontWeight: '500',
}}
>
Previous
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{
paddingHorizontal: 16,
paddingVertical: 8,
backgroundColor:
currentStep < steps.length - 1 ? '#007AFF' : '#e5e7eb',
borderRadius: 6,
}}
onPress={() =>
setCurrentStep(Math.min(steps.length - 1, currentStep + 1))
}
disabled={currentStep === steps.length - 1}
>
<Text
style={{
color: currentStep < steps.length - 1 ? '#fff' : '#999',
fontWeight: '500',
}}
>
Next
</Text>
</TouchableOpacity>
</View>
</View>
);
}
API Reference
Progress
The main progress bar component.
Prop | Type | Default | Description |
---|---|---|---|
value | number | - | The progress value between 0-100. |
style | ViewStyle | - | Additional styles to apply to the progress container. |
height | number | 4 | The height of the progress bar in pixels. |
onValueChange | (value: number) => void | - | Callback fired when the progress value changes (interactive). |
onSeekStart | () => void | - | Callback fired when seeking starts (interactive). |
onSeekEnd | () => void | - | Callback fired when seeking ends (interactive). |
interactive | boolean | false | Whether the progress bar can be interacted with (tap/drag). |
Accessibility
The Progress component is built with accessibility in mind:
- Uses semantic structure for screen readers
- Supports gesture-based interaction when interactive
- Proper touch targets for interactive elements
- Smooth animations with reduced motion support
- Clear visual feedback for progress states
Interactive Features
When interactive
is set to true
, the Progress component supports:
- Tap to seek: Tap anywhere on the progress bar to jump to that position
- Drag to scrub: Drag the progress indicator to scrub through values
- Smooth animations: Animated transitions between values
- Callbacks: Get notified when seeking starts, changes, or ends
This makes it perfect for media players, volume controls, or any scenario where users need to adjust a value by interacting with the progress bar.