suiss/uim · React Native Component
Progress
A horizontal bar that shows how far a task has progressed, with an optional label and value.
Progress is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.


import * as React from "react"
import { Progress } from "@/components/ui/progress"
function ProgressDemo() {
const [value, setValue] = React.useState(13)
React.useEffect(() => {
const timer = setTimeout(() => setValue(66), 500)
return () => clearTimeout(timer)
}, [])
return <Progress value={value} />
}
export function ProgressExample() {
return (
<ProgressDemo />
)
}import EsuissUI
import SwiftUI
struct ProgressDelayed: View {
@State private var value = 13.0
var body: some View {
ProgressView(value: value, total: 100)
.progressViewStyle(.bar)
.task {
try? await Task.sleep(for: .milliseconds(500))
value = 66
}
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.Progress
import kotlinx.coroutines.delay
@Composable
fun ProgressDefault() {
var value by rememberSaveable { mutableFloatStateOf(13f) }
LaunchedEffect(Unit) {
delay(500)
value = 66f
}
Progress(value, Modifier.fillMaxWidth())
}Progress renders a thin track with an indicator whose width follows value. The width animates with a Reanimated spring, clamped between 0 and 100 percent. Any children, such as ProgressLabel and ProgressValue, are placed in a row above the bar.
Use it for uploads, downloads and other tasks with a known amount of work.
Installation
$ pnpm dlx @esuiss/uim@latest add progress$ pnpm dlx @esuiss/uim@latest add progress --platform swift$ pnpm dlx @esuiss/uim@latest add progress --platform kotlinManual
- Install the dependencies:
npx expo install cn react-native-reanimated react-native-worklets-
Add the pieces it builds on: the primitives
components/primitives/progress.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/progress.tsx:
import { cn } from "cn"
import Animated, {
Extrapolation,
interpolate,
useAnimatedStyle,
useDerivedValue,
withSpring,
} from "react-native-reanimated"
import * as ProgressPrimitive from "@/components/primitives/progress"
import { resolveClassName } from "@/components/primitives/use-render"
function Progress({
className,
children,
trackClassName,
indicatorClassName,- Update the import paths to match your project.
- Install the dependencies:
npx expo install cn react-native-reanimated react-native-worklets-
Add the pieces it builds on: the primitives
components/primitives/progress.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/progress.tsx:
import { cn } from "cn"
import Animated, {
Extrapolation,
interpolate,
useAnimatedStyle,
useDerivedValue,
withSpring,
} from "react-native-reanimated"
import * as ProgressPrimitive from "@/components/primitives/progress"
import { resolveClassName } from "@/components/primitives/use-render"
function Progress({
className,
children,
trackClassName,
indicatorClassName,- Update the import paths to match your project.
- Install the dependencies:
npx expo install react-native-reanimated react-native-worklets-
Add the pieces it builds on: the primitives
components/primitives/progress.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/progress.tsx:
import { type StyleProp, StyleSheet, type ViewStyle } from "react-native"
import Animated, {
Extrapolation,
interpolate,
useAnimatedStyle,
useDerivedValue,
withSpring,
} from "react-native-reanimated"
import * as ProgressPrimitive from "@/components/primitives/progress"
import { fontSize, radius, useTheme } from "@/components/ui/theme"
const styles = StyleSheet.create({
root: {
width: "100%",
flexDirection: "row",
flexWrap: "wrap",- Update the import paths to match your project.
-
Target iOS 17 or later.
-
Copy the theme files every view uses:
import SwiftUI
import UIKit
struct RGBA: Sendable {
let red: CGFloat
let green: CGFloat
let blue: CGFloat
let alpha: CGFloat
init(_ hex: UInt32, _ alpha: CGFloat = 1) {
red = CGFloat((hex >> 16) & 0xFF) / 255
green = CGFloat((hex >> 8) & 0xFF) / 255
blue = CGFloat(hex & 0xFF) / 255
self.alpha = alpha
}import SwiftUI
extension Color {
public static let surface = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
public static let surfaceSecondary = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
public static let surfaceTertiary = Color.adaptive(light: .init(0xE8E8ED), dark: .init(0x2C2C2E))
public static let surfaceRaised = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
public static let label = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let labelSecondary = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
public static let labelTertiary = Color.adaptive(light: .init(0x86868B), dark: .init(0x86868B))
public static let labelQuaternary = Color.adaptive(light: .init(0xD2D2D7), dark: .init(0x424245))
public static let control = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
public static let controlPressed = Color.adaptive(light: .init(0x000000, 0.12), dark: .init(0xFFFFFF, 0.22))
public static let controlSelected = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let controlDisabled = Color.adaptive(light: .init(0x000000, 0.03), dark: .init(0xFFFFFF, 0.05))
public static let onControlSelected = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))- Copy the source and every file it uses:
import SwiftUI
public struct BarProgressViewStyle: ProgressViewStyle {
public init() {}
public func makeBody(configuration: Configuration) -> some View {
BarProgress(configuration: configuration)
}
}
private struct BarProgress: View {
let configuration: ProgressViewStyleConfiguration
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {import SwiftUI
public enum TextVariant: CaseIterable, Sendable {
case `default`, h1, h2, h3, h4, p, blockquote, code, lead, large, small, muted
var style: TextStyle {
switch self {
case .h1: .xl3
case .h2: .xl2
case .h3: .xl
case .h4, .lead, .large: .lg
case .code, .small, .muted: .sm
default: .base
}
}- Add the files to your app target.
- Set
minSdkto 24 or higher and add the dependencies:
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")- Copy the theme files every view uses:
package com.esuiss.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffsetpackage com.esuiss.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Immutable
data class Colors(
val surface: Color,
val surfaceSecondary: Color,
val surfaceTertiary: Color,
val surfaceRaised: Color,
val label: Color,
val labelSecondary: Color,
val labelTertiary: Color,
val labelQuaternary: Color,- Copy the source:
package com.esuiss.ui.components
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicText- Update the
com.esuiss.uipackage names to match your project.
Usage
import { Progress, ProgressTrack, ProgressIndicator, ProgressLabel, ProgressValue } from "@/components/ui/progress"<ProgressDemo />Set value between min and max (0 and 100 by default). Pass null when the progress is unknown.
Add a ProgressLabel and a ProgressValue as children to show a caption and the formatted value; the value is pushed to the end of the row. Format the value with format and locale.
Style the bar with trackClassName and indicatorClassName. ProgressTrack and ProgressIndicator are also exported for custom layouts.
Examples
With label
A bar with a label and a live percentage value.


import * as React from "react"
import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress"
function WithLabelDemo() {
const [value, setValue] = React.useState(0)
React.useEffect(() => {
const timer = setInterval(() => {
setValue((current) => (current >= 100 ? 0 : current + 10))
}, 600)
return () => clearInterval(timer)
}, [])
return (
<Progress value={value}>
<ProgressLabel>Uploading</ProgressLabel>
<ProgressValue />
</Progress>
)
}
export function ProgressWithLabelExample() {
return (
<WithLabelDemo />
)
}import EsuissUI
import SwiftUI
struct ProgressWithLabel: View {
@State private var value = 0
var body: some View {
ProgressView(value: Double(value), total: 100) {
Text("Uploading")
} currentValueLabel: {
Text(verbatim: "\(value)%")
}
.progressViewStyle(.bar)
.task {
while !Task.isCancelled {
try? await Task.sleep(for: .milliseconds(600))
value = value >= 100 ? 0 : value + 10
}
}
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.Progress
import kotlinx.coroutines.delay
@Composable
fun ProgressWithLabel() {
var value by rememberSaveable { mutableFloatStateOf(0f) }
LaunchedEffect(Unit) {
while (true) {
delay(600)
value = if (value >= 100f) 0f else value + 10f
}
}
Progress(value, Modifier.fillMaxWidth(), label = "Uploading", showValue = true)
}Complete
A bar at 100 percent.


import { Progress } from "@/components/ui/progress"
export function ProgressCompleteExample() {
return (
<Progress value={100} />
)
}import EsuissUI
import SwiftUI
struct ProgressCompleteExample: View {
var body: some View {
ProgressView(value: 100, total: 100)
.progressViewStyle(.bar)
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.Progress
@Composable
fun ProgressCompleteExample() {
Progress(100f, Modifier.fillMaxWidth())
}Values
Bars at 0, 25, 50, 75 and 100 percent, the last with a custom indicator color.


import { View } from "react-native"
import { Progress } from "@/components/ui/progress"
export function ProgressValuesExample() {
return (
<View className="w-full gap-4">
<Progress value={0} />
<Progress value={25} />
<Progress value={50} />
<Progress value={75} />
<Progress value={100} indicatorClassName="bg-green" />
</View>
)
}import EsuissUI
import SwiftUI
struct ProgressValuesExample: View {
var body: some View {
VStack(spacing: 16) {
ForEach([0.0, 25, 50, 75, 100], id: \.self) { value in
ProgressView(value: value, total: 100)
.progressViewStyle(.bar)
}
}
.frame(maxWidth: .infinity)
}
}import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Progress
@Composable
fun ProgressValues() {
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
listOf(0f, 25f, 50f, 75f, 100f).forEach { Progress(it, Modifier.fillMaxWidth()) }
}
}API Reference
Progress
Root that holds the value and renders the track and indicator.
React.ComponentProps<typeof ProgressPrimitive.Root>- About
- Extra classes for the track.
- Type
string- Default
- —
- About
- Extra classes for the indicator.
- Type
string- Default
- —
ProgressTrack
Background bar.
React.ComponentProps<typeof ProgressPrimitive.Track>ProgressIndicator
Animated fill that shows the current value.
React.ComponentProps<typeof ProgressPrimitive.Indicator>ProgressLabel
Caption text above the bar.
React.ComponentProps<typeof ProgressPrimitive.Label>ProgressValue
Formatted value text, aligned to the end.
React.ComponentProps<typeof ProgressPrimitive.Value>Accessibility
- The root has the
progressbarrole and reportsmin,max, the current value and a value text, so VoiceOver and TalkBack announce the progress. - Use
getAccessibilityValueTextto customize the announced text.
Notes
- The track uses
bg-surface-secondaryand the indicatorbg-accent. - Label and value text use
text-labelandtext-label-secondary, with tabular numbers for the value. - Light and dark colors follow the system appearance.