Building A Step Progress Indicator View In Flutter

In this post, we will learn how to make a dynamic step progress indicator view in a Flutter application.


In any app, usually we will have to build some kind of Form to collect input from users. Traditionally apps used to have long and boring Forms. Imagine you are suddenly presented with this kind of form when you open any app.

A Boring Long Form
A Boring Long Form

Yes, look for the nearest exit!

DecisionMentor app

However, in today’s modern apps, we often find a multi-step fashioned forms. These kind of forms are progressive and visually appealing to the users.

Step Progress Indicator
Step Progress Indicator

The element that makes the Form progressive is the Step Progress Indicator that always stays on the top of the page.

Code Setup: Step Progress Indicator

First of all, let’s identify the variables that might play a part to display this step progress indicator view.

//height of the container
final double _height;
//width of the container
final double _width;
//container decoration
final BoxDecoration decoration;
//list of texts to be shown for each step
final List<String> _stepsText;
//cur step identifier
final int _curStep;
//active color
final Color _activeColor;
//in-active color
final Color _inactiveColor;
//dot radius
final double _dotRadius;
//container padding
final EdgeInsets padding;
//line height
final double lineHeight;
//header textstyle
final TextStyle _headerStyle;
//steps text
final TextStyle _stepStyle;

The variable _stepsText can enable us to have a dynamic list of steps: 3, 4 or 10 step form.

You can initialize all variables via the constructor like so:

const StepProgressView(
    List<String> stepsText,
    int curStep,
    double height,
    double width,
    double dotRadius,
    Color activeColor,
    Color inactiveColor, 
    TextStyle headerStyle,
    TextStyle stepsStyle,
    Key key,
    this.lineHeight = 2.0,
  })  : _stepsText = stepsText,
        _curStep = curStep,
        _height = height,
        _width = width,
        _dotRadius = dotRadius,
        _activeColor = activeColor,
        _inactiveColor = inactiveColor,
        _headerStyle = headerStyle,
        _stepStyle = stepsStyle,
        assert(curStep > 0 == true && curStep <= stepsText.length),
        assert(width > 0),
        assert(height >= 2 * dotRadius),
        assert(width >= dotRadius * 2 * stepsText.length),
        super(key: key);

Now, to build the dots and progress line, we can use the function below:

List<Widget> _buildDots() {
    var wids = <Widget>[];
    _stepsText.asMap().forEach((i, text) {
      var circleColor = (i == 0 || _curStep > i + 1)
                        ? _activeColor
                        : _inactiveColor;
      var lineColor = _curStep > i + 1
                      ? _activeColor
                      : _inactiveColor;

        radius: _dotRadius,
        backgroundColor: circleColor,
      //add a line separator
      if (i != _stepsText.length - 1) {        
            child: Container(height: lineHeight, color: lineColor,)

    return wids;

Similarly, to build the texts underneath each step, we can use this function:

List<Widget> _buildText() {
    var wids = <Widget>[];
    _stepsText.asMap().forEach((i, text) {
      wids.add(Text(text, style: _stepStyle));

    return wids;

Once you have these identified, all you gotta do is just build the view 🙂

Widget build(BuildContext context) {
    return Container(
        padding: padding,
        height: this._height,
        width: this._width,
        decoration: this.decoration,
        child: Column(
          children: <Widget>[
              child: Center(
                child: RichText(
                  text: TextSpan(
                    children: [
                        text: (_curStep).toString(),
                        style: _headerStyle.copyWith(
                          color: _activeColor,//this is always going to be active
                        text: " / " + _stepsText.length.toString(),
                        style: _headerStyle.copyWith(
                          color: _curStep == _stepsText.length
                          ? _activeColor
                          : _inactiveColor,
              children: _buildDots(),
            SizedBox(height: 10,),
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: _buildText(),

Demo With A PageView Widget

Now that our step progress indicator view is ready, let’s use this in a demo app. We can make use of the PageView widget to build the multiple pages of our Form.

//Build method of Main Page
Widget build(BuildContext context) {
    var mediaQD = MediaQuery.of(context);
    _safeAreaSize = mediaQD.size;
    return Scaffold(
        body: Column(
      children: <Widget>[
        Container(height: 150.0, child: _getStepProgress()),
          child: PageView(
          onPageChanged: (i) {
            setState(() {
              _curPage = i + 1;
          children: <Widget>[

Initializing Our StepProgressView

final _stepsText = ["About you", "Some more..", "Your credit card details"];

  final _stepCircleRadius = 10.0;

  final _stepProgressViewHeight = 150.0;

  Color _activeColor = Colors.lightBlue;

  Color _inactiveColor = Colors.grey;

  TextStyle _headerStyle =
      TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold);

  TextStyle _stepStyle = TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold);

  Size _safeAreaSize;

  int _curPage = 1;

  StepProgressView _getStepProgress() {
    return StepProgressView(
      decoration: BoxDecoration(color: Colors.white),
      padding: EdgeInsets.only(
        top: 48.0,
        left: 24.0,
        right: 24.0,

Complete code for this tutorial is in github as well.

Related Posts:

Flutter: Adding A Progress Bar Animation In Page View