Skip to content
Blog

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.