Creating a Custom Stepper in Flutter: A Comprehensive Guide

3 min readJan 3, 2025

In this article, we’ll learn how to build a custom stepper widget in Flutter to create smooth, user-friendly navigation for multi-step processes like forms, checkout flows, or onboarding experiences.

With this step-by-step guide, you’ll see how Flutter’s widget tree, state management, and custom components make creating reusable UI widgets effortless.

Why Build a Custom Stepper?

While Flutter provides a built-in Stepper widget, creating a custom stepper allows for greater flexibility in design and functionality. By building your own, you can tailor every detail to your app's specific needs, from UI/UX customization to dynamic step validation.

What We’ll Cover

  • Creating the parent widget to manage the stepper flow.
  • Building the reusable CustomStepper widget.
  • Designing individual steps with CustomStepperStep.
  • Adding navigation controls (Next/Previous buttons).
  • Bonus tips on optimizing and scaling your stepper widget.

Code Implementation: Step-by-Step

Step 1: Parent Widget

The parent widget manages the current active step and handles navigation logic. Here’s how we set it up:

import 'package:flutter/material.dart';
import 'package:your_project/widgets/custom_stepper.dart';
class NewOrder extends StatefulWidget {
const NewOrder({super.key});
@override
State<NewOrder> createState() => _NewOrderState();
}
class _NewOrderState extends State<NewOrder> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Nouvelle Commande"),
),
body: Column(
children: [
CustomStepper(
initialActiveIndex: _currentIndex,
goToNextStep: () {
if (_currentIndex < 2) {
setState(() {
_currentIndex++;
});
}
},
goToPreviousStep: () {
if (_currentIndex > 0) {
setState(() {
_currentIndex--;
});
}
},
steps: [
CustomStepperStep(
icon: Icons.info,
content: const Text("Basic Information"),
),
CustomStepperStep(
icon: Icons.location_on,
content: const Text("Location Details"),
),
CustomStepperStep(
icon: Icons.shopping_cart,
content: const Text("Order Details"),
),
],
),
],
),
);
}
}

Step 2: The CustomStepper Widget

The CustomStepper widget handles rendering the stepper UI and content.

import 'package:flutter/material.dart';
class CustomStepper extends StatefulWidget {
final List<CustomStepperStep> steps;
final int initialActiveIndex;
final VoidCallback goToNextStep;
final VoidCallback goToPreviousStep;
const CustomStepper({
super.key,
required this.steps,
this.initialActiveIndex = 0,
required this.goToNextStep,
required this.goToPreviousStep,
});
@override
State<CustomStepper> createState() => _CustomStepperState();
}
class _CustomStepperState extends State<CustomStepper> {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: widget.steps.asMap().entries.map((entry) {
int index = entry.key;
CustomStepperStep step = entry.value;
return Row(
children: [
// Step Circle
Container(
padding: const EdgeInsets.all(4),
width: 38,
height: 38,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: index <= widget.initialActiveIndex
? step.color ?? Colors.blue
: Colors.grey.shade300,
),
child: Icon(
step.icon,
color: index <= widget.initialActiveIndex
? Colors.white
: Colors.grey,
),
),
if (index < widget.steps.length - 1)
Expanded(
child: Divider(
thickness: 2,
color: index < widget.initialActiveIndex
? Colors.blue
: Colors.grey.shade300,
),
),
],
);
}).toList(),
),
const SizedBox(height: 20),
widget.steps[widget.initialActiveIndex].content,
const SizedBox(height: 20),
// Navigation Buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: widget.initialActiveIndex > 0
? widget.goToPreviousStep
: null,
child: const Text("Back"),
),
ElevatedButton(
onPressed: widget.initialActiveIndex <
widget.steps.length - 1
? widget.goToNextStep
: null,
child: const Text("Next"),
),
],
),
],
),
);
}
}

Step 3: Custom Step Definition

Each step in the stepper is defined using CustomStepperStep.

class CustomStepperStep {
final IconData icon;
final Widget content;
final Color? color;

CustomStepperStep({
required this.icon,
required this.content,
this.color,
});
}

Optimizing for Accessibility

  • Responsive Design: Ensure your stepper scales on different devices using Flutter’s MediaQuery.
  • Keyboard Navigation: Add shortcuts for accessibility users with FocusTraversalGroup.
  • Custom Colors: Let users define custom colors or gradients for step indicators.

Best Practices

  1. Reusable Widgets: Use CustomStepper in multiple parts of your app with varying content.
  2. State Management: Use providers like Riverpod or Bloc for larger apps.
  3. Animations: Add step transition animations with AnimatedSwitcher.

Flutter Community #FlutterStepper

Let me know your thoughts or suggestions in the comments. Happy coding! 🚀

--

--

No responses yet