---
title: Progress
description: Progress shows a wait: a spinner when the end is unknown, a bar or ring when it is known. Appetite UI Figma component with a typing indicator and tokens.
group: Components
source: https://www.appetiteui.com/docs/progress
dateModified: 2026-10-03
---

# Progress

Updated: October 3, 2026.

Progress shows that work is happening: a spinner when the wait has no end in sight, a bar or a ring when it does, and three dots when a reply is being written.

## Preview

Two ways to say the app is busy. Spinner when you cannot tell how long, bar when you can. On iOS both are ProgressView, on Android CircularProgressIndicator and LinearProgressIndicator.

Specimen: Spinner base 24, Progress Bar at 50. both on brand/base over bg/soft-100.

The spinner has 8 frames at 80 ms, so a full turn takes 640 ms. In code that is one rotating arc, and Reduce Motion stops it.

Progress Ring shows a known total as a circle and Typing Indicator shows three dots for a reply that is being written. Both have their own section below.

Source: the Progress page of the Appetite UI Figma file, read on October 2, 2026. Four sets sit on it, one per section.

## Which sizes does Spinner come in?

Spinner has four sizes crossed with eight frames, 32 variants. The arc is identical in all of them and only the box changes.

Specimen: sm 16, md 20, base 24, lg 32. ring is 28 percent of the radius: 2.24, 2.8, 3.36, 4.48.

Specimen: Frame 1 to 8, held still. 45 degrees apart, eight of them close the circle.

Every frame is a track and a 270 degree indicator, both at an inner radius of 0.72, so the ring is 28 percent of the radius and scales with the box.

The frames exist so the animation can live in Figma. In code, rotate one arc and never expose Frame.

## How big is a Progress Bar?

Progress Bar is 320 by 8, clipping, with an indicator inside it. Five variants set the width and nothing else changes.

Specimen: Value 0, 25, 50, 75, 100. indicator widths 0.01, 80, 160, 240, 320.

At Value 0 the indicator is a real node 0.01 wide, so it rounds to nothing but is still there.

Value is a five stop axis, so real progress means overriding the width on the instance. Bind it to the real number and never animate it for decoration.

## What is Progress Ring for?

The bar as a circle, for one figure that deserves room. A 48 box, five values and an optional percent label.

Specimen: Value 0, 25, 50, 75, 100. 48 x 48, track and indicator 40 at stroke 4.

Progress Ring is a 48 box with a 40 circle drawn at stroke 4: the track in bg/soft-100 and the indicator in brand/base with round caps, starting at 12 o'clock. The Show label boolean prints the percent in the middle in xs/emphasized and is on by default.

At Value 0 the indicator is hidden. The ring resizes freely, so draw it at the size the screen needs and keep the label only where it still fits.

Specimen: Show label off, at 24 and 32. boxes 24 and 32, the stroke stays 4.

## What is Typing Indicator for?

Three dots that say a reply is being written. It loops with no value, for chat and assistant replies.

Specimen: Typing Indicator, running, then Frame 1 to 4 held still. 40 x 14, dots 8 on a pitch of 16, one cycle 1200 ms.

The set is 40 by 14: three dots of 8 on a pitch of 16, in fg/soft-500. The active dot stands at the top at full opacity and the other two rest 3 lower at 35 percent.

Frame 1 raises dot 1, Frame 2 dot 2, Frame 3 dot 3 and Frame 4 none. Each frame is 100 ms of dwell and 200 ms of travel on linear easing, so one cycle takes 1.2 seconds.

Pair it with AI / Message set to Streaming, described on the [AI component](https://www.appetiteui.com/docs/ai-component) page. With Reduce Motion on, the first stage specimen rests on Frame 1.

## Properties

Four separate sets. Spinner and Typing Indicator animate through a Frame axis, Progress Bar and Progress Ring through Value.

| Property | Type | Default |
| --- | --- | --- |
| `Size` | sm \| md \| base \| lg, on Spinner | sm |
| `Frame` | 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8, on Spinner | 1 |
| `Value` | 0 \| 25 \| 50 \| 75 \| 100, on Progress Bar | 0 |
| `Value` | 0 \| 25 \| 50 \| 75 \| 100, on Progress Ring | 0 |
| `Show label` | Boolean, on Progress Ring | true |
| `Frame` | 1 \| 2 \| 3 \| 4, on Typing Indicator | 1 |

| Set | Variants | Size |
| --- | --- | --- |
| `Spinner` | 32, Size crossed with Frame | 16, 20, 24, 32 square |
| `Progress Bar` | 5, on Value | 320 x 8 |
| `Progress Ring` | 5, on Value | 48 x 48 |
| `Typing Indicator` | 4, on Frame | 40 x 14 |

Frame is a timeline. Expose Size and Value in code and keep Frame inside the animation.

No set carries a buffer track or an indeterminate bar.

## Tokens

Four colors across the four sets.

| Token | Light | Dark | Applied to |
| --- | --- | --- | --- |
| `bg/soft-100` | #E9E9EA | #48484A | Spinner track, bar track, ring track |
| `brand/base` | #155DFC | #2B7FFF | Spinner arc, bar indicator, ring indicator |
| `text/strong-950` | #0E0E0F | #FFFFFF | Ring label |
| `fg/soft-500` | #8E8E93 | #AEAEB2 | Typing Indicator dots |

In light the track resolves to `colors/gray/100`, in dark to `colors/gray/700`. Brand goes one step lighter in dark.

No set has a success, warning or error color.

## How does Progress differ on iOS and Material 3?

Both platforms ship a spinner and a bar and animate them natively. Appetite UI draws the same two, adds a ring and a typing indicator, and the file has no Material 3 progress page to compare.

| Appetite UI | iOS counterpart | Material 3 counterpart | What differs |
| --- | --- | --- | --- |
| Spinner | `iOS / Spinner` in the Apple pages, Small 14, Regular 20 and Large 35. In code SwiftUI `ProgressView()` or UIKit `UIActivityIndicatorView` | `CircularProgressIndicator`, indeterminate | Appetite UI holds eight frames at 80 ms on a 270 degree arc. The platforms animate it natively, so in code rotate one arc. |
| Progress Bar | `iOS / Progress Bar` in the Apple pages, 370 x 4 at Value 0 to 100 percent. In code `ProgressView(value:total:)` | `LinearProgressIndicator` | Appetite UI is 320 by 8 at radius full with five Values to design against. Bind the real number in code. |
| Progress Ring | No built in ring. A `Gauge` or a custom progress style draws one. | `CircularProgressIndicator` with a progress value | Appetite UI draws a 48 box with a stroke of 4, round caps and an optional percent label. |
| Typing Indicator | None. Messages draws its own typing bubble. | None | A loop of three dots, 1.2 seconds a cycle, for chat and assistant replies. |

None of the four is a control, so none has a touch target. Anything a person can tap next to them, such as a cancel button, keeps the minimum of 44 pt on iOS and 48 dp on Android, set by `platform/min-touch-target` in the Platform collection.

## When should I use a spinner and when a bar?

Ask one question: do you know the total? If you do, use the bar or the ring bound to the real number. If you do not, use the spinner.

- Do: Let the spinner promise nothing but that something is happening. Sign in, a network call, a sync with no total: that is exactly the wait it was drawn for.

- Avoid: Never put a percentage next to the spinner. If you can count it, the bar shows it properly. If you cannot, the number is a guess and people will hold you to it.

For a flow with a known number of steps, use [Progress Steps](https://www.appetiteui.com/docs/progress-steps). Uploads, downloads and multi step forms know the total, so they get the bar or the ring.

Everything else gets the spinner. Use the smallest size the surface allows, keep one on screen at a time, and past a few seconds say what is taking so long.

Motion alone says nothing to a screen reader. In code give a bar or ring a progressbar role with its value and a name, and announce that a spinner means loading.

With Reduce Motion on, swap the spin for a static arc or a line of text, and let the typing dots rest on Frame 1.

## In the screens and the glossary

- Glossary: [Skeleton screen](https://www.appetiteui.com/academy/glossary/skeleton-screen)
