Skip to content

Progress indicators ​

LoaderKitProgress shows how much of a task is done. It has 10 types and 50 designs, and each one runs with a value or, when the value is unknown, indeterminate. When the value changes, the indicator glides to it. Click a design to open its panel: change the value, thickness, size and color, then copy the code for your platform.

Built-in or progress?

  • No percentage, ever (waiting for a request, pull to refresh): use a built-in indicator. There are 50 styles to pick from.
  • The task has progress, even if it is unknown at first (a download, an upload, processing a file): use a progress indicator from this page. Start it indeterminate with value null (nil in Swift), then set a value once you know it. It is the same component, so nothing else changes.
0.40

Progress indicators are not JSON specs. Every type is drawn from the same geometry on every platform: a reference implementation turns the options and the animation state into draw commands, and each platform runs the same test vectors against its own port. A design looks and moves the same on the web, Android, iOS, macOS and Windows.

Quick start ​

html
<script type="module">
  import '@loader-kit/web/progress-element';
</script>

<!-- Indeterminate circular, 48 × 48 -->
<loader-kit-progress></loader-kit-progress>

<loader-kit-progress type="linear" value="0.4"></loader-kit-progress>
<loader-kit-progress type="gauge" value="0.7" show-label size="64"></loader-kit-progress>
tsx
import { LoaderKitProgress } from '@loader-kit/web/react';

<LoaderKitProgress type="linear" value={progress} />
<LoaderKitProgress value={done ? 1 : null} />
vue
<script setup lang="ts">
import { LoaderKitProgress } from '@loader-kit/web/vue';
</script>

<template>
  <LoaderKitProgress type="linear" :value="progress" />
</template>
svelte
<script>
  import { LoaderKitProgress } from '@loader-kit/web/svelte';
</script>

<LoaderKitProgress type="linear" value={progress} />
xml
<io.github.maitrungduc1410.loaderkit.LoaderKitProgressView
    android:id="@+id/progress"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:progressType="linear"
    app:progressValue="0.4" />
kotlin
LoaderKitProgress(
    value = progress,
    modifier = Modifier.fillMaxWidth(),
    type = ProgressType.Linear,
)
swift
let progress = LoaderKitProgressView(value: 0.4, type: .linear)
progress.value = 0.8   // glides to 0.8
swift
LoaderKitProgress(value: progress, type: .linear)
xml
<lk:LoaderKitProgress Type="Linear" Value="{x:Bind ViewModel.Progress, Mode=OneWay}" />

Value ​

value goes from 0 to 1. Values outside that range are clamped. null (nil in Swift), or NaN, shows the indeterminate animation, so the same view covers "we do not know yet" and "40% done":

ts
const view = new LoaderKitProgressView(host, { type: 'linear' });  // indeterminate
view.value = 0.25;
view.value = null;   // back to indeterminate
kotlin
progress.value = 0.25
progress.value = null
swift
progress.value = 0.25
progress.value = nil
csharp
progress.Value = 0.25;
progress.Value = null;

buffer adds a second, lighter bar ahead of the value on linear flat and wavy, like the loaded part of a video.

Smooth value changes ​

smooth is on by default. A new value does not jump: the indicator glides to it, and the percentage label counts along. The glide follows the rhythm of the updates:

  • A stream of updates close together, like the bytes of a download, moves at a steady pace instead of stopping at each one.
  • An update on its own takes half a second and slows down at the end.
  • Going back, for example to 0, takes 0.4 seconds.
  • The drawn value never passes the real value, and never moves backward while the value goes up.

Screen readers always read the real value, not the drawn one. Turn smooth off to draw every value as it comes:

html
<loader-kit-progress type="linear" value="0.4" smooth="false"></loader-kit-progress>
tsx
<LoaderKitProgress type="linear" value={progress} smooth={false} />
kotlin
LoaderKitProgress(value = progress, type = ProgressType.Linear, smooth = false)
swift
LoaderKitProgress(value: progress, type: .linear).smooth(false)
xml
<lk:LoaderKitProgress Type="Linear" Value="{x:Bind ViewModel.Progress, Mode=OneWay}" Smooth="False" />

Types and variants ​

type picks the shape and variant its style. A variant the type does not have falls back to the first one in its list.

TypeVariantsSize without layout constraints
linearflat, wavy, segmented, striped, shimmer, glow, dots, steps, gradient, center, chevrons, ticksfills the width; the height follows the thickness
circular (default)flat, wavy, segmented, gradient, ticks, dots, glow, split, orbit, dualsize × size
pieflat, segmentedsize × size
gaugeflat, segmented, needle, gradient, dotssize × size
liquidflat, heartsize × size
borderflat, glow, segmentedwraps its content
barsflat, dots, arcssize × 0.75 size
gridflat, dotssize × size
batteryflat, segmentedsize × 0.5 size
hourglassflatsize × size

size is a number of pixels (dp on Android, points on Apple), 48 by default; unlike LoaderKit, it takes no CSS lengths. Below 32, circular wavy draws flat, because the wave would not read at that size. hourglass has no room for the percentage, so it ignores showLabel.

Some designs move even with a fixed value: the waves of wavy and liquid, the stripes of striped and the sheen of shimmer.

Options ​

OptionDefaultApplies to
thicknessdepends on the type and variantwidth of strokes and bars
trackGap4space between the progress and the track, or between segments
segmentsdepends on the type and variantsegments, dots, ticks, steps, bars or grid columns
showLabelfalsethe percentage, inside or next to the indicator
stopIndicatortruethe dot at the end of the track of linear flat and wavy
strokeCaproundstroke ends: round or butt
amplitude, wavelength, waveSpeed3, 40, 1 for linear; 2, 15, 1 for circularthe wave of wavy
sweepAngle270the arc of gauge, in degrees from 30 to 350
cornerRadius12the corners of border
speed1playback rate of the indeterminate animation; 0 or less pauses it
colorthe accent color; on the web, the CSS colorthe progress
trackColorcolor at 24% opacitythe track
labelColorthe text color; on the web, the CSS colorthe percentage
respectsReduceMotiontruesee Reduced motion

Lengths are CSS px on the web, dp on Android, points on Apple platforms and effective pixels on Windows.

Names on each platform ​

Concept<loader-kit-progress>React, Vue, Svelte, Android, Compose, SwiftWindows
ValuevaluevalueValue
Smooth changessmoothsmoothSmooth
Optionstrack-gap, show-label, stop-indicator, stroke-cap, wave-speed, sweep-angle, corner-radiustrackGap, showLabel, stopIndicator, strokeCap, waveSpeed, sweepAngle, cornerRadiusTrackGap, ShowLabel, StopIndicator, StrokeCap, WaveSpeed, SweepAngle, ProgressCornerRadius
Colorscolor, track-color, label-colorcolor, trackColor, labelColorColor, TrackColor, LabelColor

On SwiftUI every option is a modifier (.thickness(6), .showLabel()). In Android XML every option is app:progress followed by its name (app:progressType, app:progressValue, app:progressShowLabel, app:progressSweepAngle, app:progressRespectsReduceMotion, ...), except the speed, which is app:speed as on LoaderKitView. On Windows, CornerRadius is already a property of every control, so the option is ProgressCornerRadius.

Content ​

A progress indicator can hold content: in the middle of circular, pie, gauge and the other square types, or inside the stroke of border, which grows to wrap it.

html
<loader-kit-progress value="0.3">
  <button aria-label="Stop">■</button>
</loader-kit-progress>

<loader-kit-progress type="border">
  <button>Upload</button>
</loader-kit-progress>
kotlin
LoaderKitProgress(value = progress) {
    IconButton(onClick = cancel) { Icon(Icons.Filled.Stop, contentDescription = "Stop") }
}
swift
LoaderKitProgress(value: progress, type: .border) {
    Button("Upload", action: upload)
}
xml
<lk:LoaderKitProgress Type="Border">
    <Button Content="Upload" Click="OnUpload" />
</lk:LoaderKitProgress>

The Android view is a ViewGroup: add children in XML or with addView. On UIKit and AppKit, set contentView to the view to show, for example progress.contentView = stopButton.

Accessibility ​

Every platform exposes the indicator as a progress bar with the value as a percentage from 0 to 100, and without a value while indeterminate. On the web, Apple platforms and Windows the default accessible name is "Loading"; Android announces a progress bar and its percentage. Say what is loading with aria-label (accessibilityLabel in the React, Vue and Svelte components), contentDescription on Android and Compose, accessibilityLabel on Apple platforms or AutomationProperties.Name on Windows.

Reduced motion ​

When the system asks for reduced motion, values jump instead of gliding, the waves, stripes and sheens stop, and the indeterminate animation runs at half speed so the indicator still shows that work is going on. See Playback for the system setting of each platform. Set respectsReduceMotion to false to keep the full motion.

Released under the MIT License.