Creating a Custom Stepper in Flutter: A Comprehensive Guide
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
- Reusable Widgets: Use
CustomStepper
in multiple parts of your app with varying content. - State Management: Use providers like Riverpod or Bloc for larger apps.
- Animations: Add step transition animations with
AnimatedSwitcher
.
Flutter Community #FlutterStepper