Progress
Progress components visually represent the completion percentage of a task. The library provides two variants:
- Progress - A linear horizontal progress bar
- ProgressCircle - A circular progress indicator
Both components feature smooth transition animations when the value changes, a loading (indeterminate) animation mode, customizable colors, and automatic styling that adapts to the current theme.
#Usage
#Progress
struct FileUpload {
uploaded: u64,
total: u64,
}
impl FileUpload {
fn progress(&self) -> f32 {
if self.total == 0 { return 0.0; }
(self.uploaded as f32 / self.total as f32) * 100.0
}
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_2()
.child(
h_flex()
.justify_between()
.child("Uploading...")
.child(format!("{:.0}%", self.progress())),
)
.child(Progress::new("upload").value(self.progress()))
}
}// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Progress, ProgressCircle } from "progress.slint";#Values
struct AppInit {
loading: bool,
progress: f32,
}
impl Render for AppInit {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_3()
.child(
h_flex()
.gap_2()
.items_center()
.child(
ProgressCircle::new("init-circle")
.loading(self.loading)
.value(self.progress)
.size_4(),
)
.child(if self.loading { "Initializing..." } else { "Ready" }),
)
.child(
Progress::new("init-bar")
.loading(self.loading)
.value(self.progress),
)
}
}Progress {
value: 0;
}
Progress {
value: 25;
}
Progress {
value: 75;
}
Progress {
value: 100;
}let values = column![
Progress::new(0.0),
Progress::new(0.25),
Progress::new(0.5),
Progress::new(0.75),
Progress::new(1.0),
]
.spacing(12);#Sizes and status colors
struct Install {
step: usize, // current package index
total: usize, // total packages
step_progress: f32,
}
impl Install {
fn overall(&self) -> f32 {
if self.total == 0 { return 0.0; }
(self.step as f32 + self.step_progress / 100.0) / self.total as f32 * 100.0
}
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_2()
.child(
h_flex()
.justify_between()
.child(format!("Package {}/{}", self.step + 1, self.total))
.child(format!("{:.0}%", self.overall())),
)
.child(Progress::new("overall").value(self.overall()))
.child(
h_flex()
.gap_2()
.items_center()
.child(Progress::new("package").value(self.step_progress).small())
.child("Current package"),
)
}
}GridLayout {
spacing: 24px;
VerticalLayout {
horizontal-stretch: 0;
spacing: 12px;
alignment: center;
Progress {
value: 50;
size: ControlSize.xsmall;
}
Progress {
value: 50;
size: ControlSize.small;
}
Progress {
value: 50;
}
Progress {
value: 50;
size: ControlSize.large;
}
}
VerticalLayout {
horizontal-stretch: 0;
spacing: 12px;
alignment: center;
Progress {
value: 30;
status: Status.info;
}
Progress {
value: 60;
status: Status.success;
}
Progress {
value: 45;
status: Status.warning;
}
Progress {
value: 80;
status: Status.danger;
}
}
}let sizes = column![
text("The four heights: 4, 6, 8 and 10 pixels.").size(14.0),
Progress::new(value).size(ControlSize::XSmall),
Progress::new(value).size(ControlSize::Small),
Progress::new(value),
Progress::new(value).size(ControlSize::Large),
text("Two of the status colours.").size(14.0),
Progress::new(value).status(Status::Success),
Progress::new(value).status(Status::Danger),
]
.spacing(12);#Progress circle
let circles = column![
text("The circle, at 8, 12, 16 and 20 across.").size(14.0),
row![
ProgressCircle::new(value).size(ControlSize::XSmall),
ProgressCircle::new(value).size(ControlSize::Small),
ProgressCircle::new(value),
ProgressCircle::new(value).size(ControlSize::Large),
ProgressCircle::new(value).diameter(48.0),
ProgressCircle::new(value).loading(app.loading),
ProgressCircle::new(value).status(Status::Warning),
]
.spacing(12)
.align_y(Alignment::Center),
]
.spacing(12);The live preview above is this sample.
GridLayout {
spacing: 16px;
VerticalLayout {
horizontal-stretch: 0;
alignment: center;
ProgressCircle {
value: root.value;
loading: root.loading;
size: ControlSize.xsmall;
}
}
VerticalLayout {
horizontal-stretch: 0;
alignment: center;
ProgressCircle {
value: root.value;
loading: root.loading;
size: ControlSize.small;
}
}
VerticalLayout {
horizontal-stretch: 0;
alignment: center;
ProgressCircle {
value: root.value;
loading: root.loading;
}
}
VerticalLayout {
horizontal-stretch: 0;
alignment: center;
ProgressCircle {
value: root.value;
loading: root.loading;
size: ControlSize.large;
}
}
VerticalLayout {
horizontal-stretch: 0;
alignment: center;
ProgressCircle {
value: 75;
status: Status.success;
size: ControlSize.large;
}
}
VerticalLayout {
horizontal-stretch: 0;
alignment: center;
ProgressCircle {
value: 40;
status: Status.warning;
size: ControlSize.large;
}
}
VerticalLayout {
horizontal-stretch: 0;
alignment: center;
ProgressCircle {
loading: true;
size: ControlSize.large;
}
}
ProgressCircle {
horizontal-stretch: 0;
value: root.value;
loading: root.loading;
width: 80px;
VerticalLayout {
alignment: center;
spacing: 2px;
Text {
text: root.loading ? "…" : "\{round(root.value)}%";
color: Theme.primary;
font-size: Theme.font-size-sm;
font-weight: 600;
horizontal-alignment: center;
}
Text {
text: "Loading";
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
horizontal-alignment: center;
}
}
}
}
GridLayout {
spacing: 8px;
VerticalLayout {
horizontal-stretch: 0;
alignment: center;
ProgressCircle {
value: root.value;
loading: root.loading;
}
}
Text {
horizontal-stretch: 0;
text: root.loading ? "Initializing…" : "Downloading…";
color: Theme.foreground;
font-size: Theme.font-size-sm;
vertical-alignment: center;
}
}#Progress
use gpui_kit::component::progress::Progress;// One row, packed left. These are small controls and always fit, so the
// wrap grid only ever broke them onto separate lines.
HorizontalLayout {
spacing: 8px;
alignment: start;
Button {
horizontal-stretch: 0;
text: "Loading";
selected: root.loading;
clicked => {
root.loading = !root.loading;
}
}
Button {
horizontal-stretch: 0;
icon: "minus";
enabled: root.value > 0;
clicked => {
root.value = max(0, root.value - 10);
}
}
Button {
horizontal-stretch: 0;
icon: "plus";
enabled: root.value < 100;
clicked => {
root.value = min(100, root.value + 10);
}
}
}
VerticalLayout {
spacing: 8px;
GridLayout {
Text {
text: root.loading ? "Preparing upload…" : "Uploading report.pdf";
color: Theme.foreground;
font-size: Theme.font-size-sm;
horizontal-stretch: 1;
}
Text {
horizontal-stretch: 0;
text: root.loading ? "" : "\{round(root.value)}%";
color: Theme.muted-foreground;
font-size: Theme.font-size-sm;
}
}
Progress {
value: root.value;
loading: root.loading;
}
}// The buttons stand in for the subscription an application would run: each
// press moves the value the bar and the loading form read, and `Loading` is
// the one that changes the form rather than the value.
let progress = column![
row![
Button::new("Advance")
.variant(Variant::Primary)
.on_press(Message::Step),
Button::new("Reset").on_press(Message::Reset),
Button::new("Loading")
.selected(app.loading)
.on_press(Message::ToggleLoading),
caption(value, app.loading),
]
.spacing(12)
.align_y(Alignment::Center),
Progress::new(value),
text(if app.loading {
"Loading: the value is the phase of the band."
} else {
"Determinate: the value is the filled width."
})
.size(14.0),
Progress::new(value).loading(app.loading),
]
.spacing(12);#Usage
Progress::new("my-progress")
.value(75.0) // 75% complete#Different Progress Values
Progress::new("progress-0").value(0.0)
Progress::new("progress-25").value(25.0)
Progress::new("progress-75").value(75.0)
Progress::new("progress-100").value(100.0)#Loading State
Use .loading(true) to show an indeterminate animation when the actual progress is unknown. The value is ignored while loading is active.
// Indeterminate loading animation
Progress::new("loading").loading(true)
// Toggle between loading and determinate
Progress::new("my-progress")
.loading(self.is_loading)
.value(self.progress)#Sizes
Progress implements the Sizable trait:
Progress::new("xs").value(50.0).xsmall() // 4px height
Progress::new("sm").value(50.0).small() // 6px height
Progress::new("md").value(50.0) // 8px height (default)
Progress::new("lg").value(50.0).large() // 10px height#Custom Style
// The page's own sample — a 16-pixel bar with a 2-pixel corner and a 2-pixel
// edge, none of which the size ladder above can express. Its `green_light`
// fill and `green` edge are the success token at half strength and whole.
let custom_style = column![
Progress::new(value)
.height(16.0)
.rounded(2.0)
.color(theme::alpha(app.tokens.success, 0.5))
.border(2.0)
.border_color(app.tokens.success),
]
.spacing(12);The component implements the Styled trait, allowing custom height, border radius, color, and border:
Progress::new("custom")
.value(32.0)
.h(px(16.))
.rounded(px(2.))
.color(cx.theme().green_light)
.border_2()
.border_color(cx.theme().green)#Dynamic Progress Updates
struct MyView {
value: f32,
is_loading: bool,
}
impl Render for MyView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_3()
.child(
h_flex()
.gap_2()
.child(
Button::new("toggle-loading")
.label("Loading")
.selected(self.is_loading)
.on_click(cx.listener(|this, _, _, cx| {
this.is_loading = !this.is_loading;
cx.notify();
})),
)
.child(Button::new("inc").icon(IconName::Plus).on_click(
cx.listener(|this, _, _, _| {
this.value = (this.value + 10.).min(100.);
}),
)),
)
.child(
Progress::new("progress")
.value(self.value)
.loading(self.is_loading),
)
}
}#API Reference
| Method | Type | Description |
|---|---|---|
new(id) | ElementId | Create a new progress bar |
value(v) | f32 | Set progress value (0–100), clamped automatically |
loading(v) | bool | Enable indeterminate loading animation; ignores value when true |
color(c) | impl Into<Hsla> | Override the fill color (defaults to theme.progress_bar) |
xsmall() / small() / large() | — | Set predefined height via Sizable |
Styled trait methods | — | Custom height, border radius, border, etc. |
#Progress
A linear progress bar. Mirrors GPUI Component's `Progress`: a 0–100 value with an eased transition, an indeterminate `loading` animation, sizes from 4px to 10px tall, and a fill color that follows `status` or an explicit `color`.
| Name | Kind | Type | Default |
|---|---|---|---|
value | in | float | |
loading | in | bool | |
size | in | ControlSize | ControlSize.medium |
status | in | Status | Status.default |
color | in | color | status == Status.default ? Theme.primary : Theme.status-color(status) |
#ProgressCircle
A circular progress indicator that displays progress as an arc. Ideal for compact spaces, inline labels, or as a download/upload indicator.
use gpui_kit::component::progress::ProgressCircle;#Usage
ProgressCircle::new("circle").value(50.0)#Loading State
// Indeterminate rotating arc animation
ProgressCircle::new("loading").loading(true)
// Toggle between loading and determinate
ProgressCircle::new("circle")
.loading(self.is_loading)
.value(self.progress)#Sizes
ProgressCircle implements the Sizable trait. Named sizes map to fixed pixel dimensions; use .size(px(n)) for custom sizes:
ProgressCircle::new("xs").value(50.0).xsmall() // size_2
ProgressCircle::new("sm").value(50.0).small() // size_3
ProgressCircle::new("md").value(50.0) // size_4 (default)
ProgressCircle::new("lg").value(50.0).large() // size_5
ProgressCircle::new("xl").value(50.0).size_20() // 80px#Custom Color
ProgressCircle::new("green").value(75.0).color(cx.theme().green)
ProgressCircle::new("yellow").value(40.0).color(cx.theme().yellow)
ProgressCircle::new("primary").value(60.0).color(cx.theme().primary)#With Inner Content
ProgressCircle implements ParentElement, so you can place content inside the circle:
ProgressCircle::new("circle-with-label")
.value(self.value)
.size_20()
.child(
v_flex()
.size_full()
.items_center()
.justify_center()
.gap_1()
.child(
div()
.child(format!("{}%", self.value as i32))
.text_color(cx.theme().progress_bar),
)
.child(div().child("Loading").text_xs()),
)#Inline with Label
h_flex()
.gap_2()
.items_center()
.child(
ProgressCircle::new("download")
.color(cx.theme().primary)
.value(self.progress)
.size_4(),
)
.child("Downloading...")#API Reference
| Method | Type | Description |
|---|---|---|
new(id) | ElementId | Create a new circular progress indicator |
value(v) | f32 | Set progress value (0–100), clamped automatically |
loading(v) | bool | Enable indeterminate loading animation; ignores value when true |
color(c) | impl Into<Hsla> | Override the arc color (defaults to theme.progress_bar) |
xsmall() / small() / large() | — | Set predefined size via Sizable |
size(px(n)) | Pixels | Set custom size |
ParentElement | — | Place content inside the circle |
#ProgressCircle
A circular progress indicator. Mirrors GPUI Component's `ProgressCircle`: a 0–100 arc drawn clockwise from the top over a tinted track, an indeterminate `loading` arc, sizes from 8px to 20px (set `width` for a custom diameter), `status`/`color`, and children centered inside the circle.
| Name | Kind | Type | Default |
|---|---|---|---|
value | in | float | |
loading | in | bool | |
size | in | ControlSize | ControlSize.medium |
status | in | Status | Status.default |
color | in | color | status == Status.default ? Theme.primary : Theme.status-color(status) |