Updated
Progress
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.
Updated
On this page
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.
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.
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.
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.
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.
What is Typing Indicator for?
Three dots that say a reply is being written. It loops with no value, for chat and assistant replies.
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 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.
Don't
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. 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.