CircularProgressIndicator in Flutter: Spinners, Arcs, and Semantics
Use Flutter's CircularProgressIndicator for centered loading states, determinate arcs, custom strokes, and accessible spinners.
Published on • November 4, 2026
AI Assistant

For centered, compact loading states — a button, a card, a screen waiting on data — CircularProgressIndicator is the default choice. This follows the cited widget guide: an indeterminate spinner when value is null, a growing arc when you supply a value between 0 and 1.
Indeterminate spinner
const Center(
child: SizedBox(
width: 36,
height: 36,
child: CircularProgressIndicator(strokeWidth: 3),
),
)
Always constrain the spinner with SizedBox; otherwise it expands to fill its parent, which is rarely what you want.
Determinate arc
CircularProgressIndicator(
value: 0.75,
strokeWidth: 6,
backgroundColor: Colors.grey.shade200,
valueColor: const AlwaysStoppedAnimation<Color>(Colors.blue),
)
valueColor is an Animation<Color> — use AlwaysStoppedAnimation for a static color, or pass a real animation to make the color shift as progress advances.
Inside a button
A common pattern is swapping a button’s label for a spinner while an action runs:
FilledButton(
onPressed: _saving ? null : _save,
child: _saving
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Save'),
)
Disable the button while saving to prevent double-submits.
Custom stroke cap and semantics
Semantics(
label: 'Loading content',
child: CircularProgressIndicator(
strokeWidth: 4,
strokeCap: StrokeCap.round,
),
)
strokeCap: StrokeCap.round gives the arc rounded ends — a small polish that reads as deliberate design.
Takeaway: Constrain your spinner with SizedBox, pass value for determinate arcs, style with strokeWidth, valueColor, and strokeCap, and add a Semantics label so screen readers announce the loading state.