Skip to main content

awesome_stepper 1.1.0 copy "awesome_stepper: ^1.1.0" to clipboard
awesome_stepper: ^1.1.0 copied to clipboard

Beautiful, customizable Flutter stepper with animated progress headers, step validation, a controller and foldable-ready large-screen layout.

example/lib/main.dart

import 'package:awesome_stepper/awesome_stepper.dart';
import 'package:flutter/material.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatefulWidget {
  const ExampleApp({super.key});

  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  ThemeMode _mode = ThemeMode.system;

  @override
  Widget build(BuildContext context) {
    const seed = Color(0xFF6750A4);
    return MaterialApp(
      title: 'Awesome Stepper',
      restorationScopeId: 'app',
      debugShowCheckedModeBanner: false,
      themeMode: _mode,
      theme: ThemeData(colorSchemeSeed: seed),
      darkTheme: ThemeData(colorSchemeSeed: seed, brightness: Brightness.dark),
      home: CheckoutPage(onToggleTheme: _toggleTheme),
    );
  }

  void _toggleTheme() {
    setState(() {
      _mode = _mode == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
    });
  }
}

/// Every awesome_stepper option in one page: the stepper on the left, the
/// Playground panel (tune icon) on the right.
class CheckoutPage extends StatefulWidget {
  final VoidCallback onToggleTheme;

  const CheckoutPage({super.key, required this.onToggleTheme});

  @override
  State<CheckoutPage> createState() => _CheckoutPageState();
}

class _CheckoutPageState extends State<CheckoutPage> {
  static const _accents = <String, Color?>{
    'Theme': null,
    'Teal': Colors.teal,
    'Orange': Colors.deepOrange,
    'Pink': Colors.pink,
  };

  final _controller = AwesomeStepperController();
  final _formKey = GlobalKey<FormState>();
  AwesomeStepperHeader _header = AwesomeStepperHeader.circle;
  AwesomeStepperLayout _layout = AwesomeStepperLayout.horizontal;
  AwesomeStepperTransition _transition = AwesomeStepperTransition.slide;
  AwesomeStepperLineStyle _line = AwesomeStepperLineStyle.solid;
  String _accent = 'Theme';
  bool _giftWrap = false;
  bool _headerJump = true;
  bool _swipe = true;
  bool _keyboard = true;
  bool _haptics = true;
  bool _sideList = true;
  bool _completedScreen = true;
  bool _customControls = false;
  bool _turkish = false;
  bool _slowPayment = true;

  @override
  void initState() {
    super.initState();
    _controller.addListener(_onStep);
  }

  @override
  void dispose() {
    _controller.removeListener(_onStep);
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Awesome Stepper'),
        actions: [
          Builder(
            builder: (context) => IconButton(
              tooltip: 'Playground',
              icon: const Icon(Icons.tune),
              onPressed: Scaffold.of(context).openEndDrawer,
            ),
          ),
          IconButton(
            tooltip: 'Toggle theme',
            icon: const Icon(Icons.dark_mode_outlined),
            onPressed: widget.onToggleTheme,
          ),
        ],
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(56),
          child: _buildHeaderPicker(),
        ),
      ),
      endDrawer: _buildPlayground(),
      body: Theme(
        data: Theme.of(context).copyWith(
          extensions: [AwesomeStepperTheme(accentColor: _accents[_accent])],
        ),
        child: _buildStepper(),
      ),
    );
  }

  Widget _buildHeaderPicker() {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
      child: SegmentedButton<AwesomeStepperHeader>(
        showSelectedIcon: false,
        segments: [
          for (final type in AwesomeStepperHeader.values)
            ButtonSegment(value: type, label: Text(type.name)),
        ],
        selected: {_header},
        onSelectionChanged: (value) => setState(() => _header = value.first),
      ),
    );
  }

  Widget _buildStepper() {
    return AwesomeStepper(
      controller: _controller,
      headerType: _header,
      layout: _layout,
      transition: _transition,
      lineStyle: _line,
      allowHeaderJump: _headerJump,
      swipeEnabled: _swipe,
      keyboardEnabled: _keyboard,
      hapticsEnabled: _haptics,
      sideBySideMinWidth: _sideList ? 840 : null,
      restorationId: 'checkout',
      nextLabel: _turkish ? 'İleri' : 'Next',
      backLabel: _turkish ? 'Geri' : 'Back',
      finishLabel: _turkish ? 'Bitir' : 'Finish',
      skipLabel: _turkish ? 'Atla' : 'Skip',
      semanticsLabel: _turkish
          ? (step, total, label) => 'Adım $step / $total: $label'
          : null,
      controlBuilder: _customControls ? _buildCustomControls : null,
      onCompleted: _showDone,
      completedBuilder: _completedScreen
          ? (_) => _buildCentered('🎉 Order placed. Thank you!')
          : null,
      steps: [
        AwesomeStepperItem(
          label: 'Address',
          subtitle: 'Validated form',
          icon: Icons.home_outlined,
          content: _buildAddressForm(),
          canContinue: () => _formKey.currentState?.validate() ?? false,
        ),
        AwesomeStepperItem(
          label: 'Extras',
          subtitle: 'Optional: Skip',
          optional: true,
          icon: Icons.redeem_outlined,
          content: _buildExtras(),
        ),
        AwesomeStepperItem(
          label: 'Gift note',
          subtitle: 'Locked until gift wrap',
          icon: Icons.edit_note_outlined,
          enabled: _giftWrap,
          content: _buildCentered('Write a short note for the gift.'),
        ),
        AwesomeStepperItem(
          label: 'Payment',
          subtitle: _slowPayment ? 'Async check: 1 s' : 'Card or wallet',
          icon: Icons.credit_card_outlined,
          content: _buildCentered('Pay with your saved card.'),
          canContinue: _slowPayment ? _checkPayment : null,
        ),
        AwesomeStepperItem(
          label: 'Review',
          subtitle: 'Check and confirm',
          icon: Icons.fact_check_outlined,
          content: _buildCentered('Everything looks good.'),
        ),
      ],
    );
  }

  Widget _buildAddressForm() {
    return Form(
      key: _formKey,
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextFormField(
              decoration: const InputDecoration(labelText: 'Full name'),
              validator: _required,
            ),
            const SizedBox(height: 16),
            TextFormField(
              decoration: const InputDecoration(labelText: 'Street'),
              validator: _required,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildExtras() {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: SwitchListTile(
        title: const Text('Gift wrap'),
        subtitle: const Text('Unlocks the gift note step'),
        value: _giftWrap,
        onChanged: (value) => setState(() => _giftWrap = value),
      ),
    );
  }

  Widget _buildCustomControls(VoidCallback onNext, VoidCallback onBack) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          IconButton.outlined(
            tooltip: 'Back',
            onPressed: onBack,
            icon: const Icon(Icons.arrow_back),
          ),
          IconButton.filled(
            tooltip: 'Next',
            onPressed: onNext,
            icon: const Icon(Icons.arrow_forward),
          ),
        ],
      ),
    );
  }

  Widget _buildPlayground() {
    return Drawer(
      width: 340,
      child: SafeArea(
        child: ListView(
          padding: const EdgeInsets.symmetric(vertical: 8),
          children: [
            _buildSection('Layout'),
            _buildChoice(
              AwesomeStepperLayout.values,
              _layout,
              (v) => _layout = v,
            ),
            _buildSection('Transition'),
            _buildChoice(
              AwesomeStepperTransition.values,
              _transition,
              (v) => _transition = v,
            ),
            _buildSection('Line style (numbered, vertical)'),
            _buildChoice(
              AwesomeStepperLineStyle.values,
              _line,
              (v) => _line = v,
            ),
            _buildSection('Accent (AwesomeStepperTheme)'),
            _buildChoice(_accents.keys.toList(), _accent, (v) => _accent = v),
            _buildSection('Behavior'),
            _buildSwitch(
              'Jump forward from the header',
              'allowHeaderJump',
              _headerJump,
              (v) => _headerJump = v,
            ),
            _buildSwitch(
              'Swipe between pages',
              'swipeEnabled',
              _swipe,
              (v) => _swipe = v,
            ),
            _buildSwitch(
              'Arrow keys and Enter',
              'keyboardEnabled',
              _keyboard,
              (v) => _keyboard = v,
            ),
            _buildSwitch(
              'Haptic feedback',
              'hapticsEnabled',
              _haptics,
              (v) => _haptics = v,
            ),
            _buildSwitch(
              'Side list on wide windows',
              'sideBySideMinWidth: 840 / null',
              _sideList,
              (v) => _sideList = v,
            ),
            _buildSwitch(
              'Async payment check',
              'canContinue: Future<bool>',
              _slowPayment,
              (v) => _slowPayment = v,
            ),
            _buildSection('Look'),
            _buildSwitch(
              'Completed screen',
              'completedBuilder',
              _completedScreen,
              (v) => _completedScreen = v,
            ),
            _buildSwitch(
              'Custom buttons',
              'controlBuilder',
              _customControls,
              (v) => _customControls = v,
            ),
            _buildSwitch(
              'Turkish labels',
              'nextLabel, backLabel, skipLabel, semanticsLabel',
              _turkish,
              (v) => _turkish = v,
            ),
            _buildSection('Controller (step ${_controller.currentStep + 1})'),
            _buildControllerButtons(),
            _buildSection('AwesomeStepIndicator (standalone)'),
            _buildIndicators(),
          ],
        ),
      ),
    );
  }

  Widget _buildSection(String title) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
      child: Text(title, style: Theme.of(context).textTheme.titleSmall),
    );
  }

  Widget _buildChoice<T>(List<T> values, T selected, void Function(T) apply) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Wrap(
        spacing: 8,
        runSpacing: 8,
        children: [
          for (final value in values)
            ChoiceChip(
              label: Text(value is Enum ? value.name : '$value'),
              selected: value == selected,
              onSelected: (_) => setState(() => apply(value)),
            ),
        ],
      ),
    );
  }

  Widget _buildSwitch(
    String title,
    String api,
    bool value,
    void Function(bool) apply,
  ) {
    return SwitchListTile(
      dense: true,
      title: Text(title),
      subtitle: Text(api, style: const TextStyle(fontFamily: 'monospace')),
      value: value,
      onChanged: (v) => setState(() => apply(v)),
    );
  }

  Widget _buildControllerButtons() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Wrap(
        spacing: 8,
        runSpacing: 8,
        children: [
          OutlinedButton(
            onPressed: _controller.back,
            child: const Text('back()'),
          ),
          OutlinedButton(
            onPressed: _controller.next,
            child: const Text('next()'),
          ),
          OutlinedButton(
            onPressed: () => _controller.jumpTo(0),
            child: const Text('jumpTo(0)'),
          ),
          OutlinedButton(
            onPressed: () => _controller.jumpTo(4),
            child: const Text('jumpTo(4)'),
          ),
        ],
      ),
    );
  }

  Widget _buildIndicators() {
    const labels = ['Address', 'Extras', 'Gift', 'Payment', 'Review'];
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 8),
      child: Column(
        children: [
          for (final type in AwesomeStepperHeader.values)
            AwesomeStepIndicator(
              type: type,
              currentStep: _controller.currentStep,
              labels: labels,
              lineStyle: _line,
              color: _accents[_accent],
              onStepTapped: _controller.jumpTo,
            ),
        ],
      ),
    );
  }

  Widget _buildCentered(String text) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(text, textAlign: TextAlign.center),
      ),
    );
  }

  String? _required(String? value) =>
      (value ?? '').trim().isEmpty ? 'Required' : null;

  Future<bool> _checkPayment() async {
    await Future<void>.delayed(const Duration(seconds: 1));
    return true;
  }

  void _onStep() => setState(() {});

  void _showDone() {
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(const SnackBar(content: Text('Order placed')));
  }
}
63
likes
160
points
178
downloads
screenshot

Documentation

API reference

Publisher

verified publishermonsware.com

Weekly Downloads

Beautiful, customizable Flutter stepper with animated progress headers, step validation, a controller and foldable-ready large-screen layout.

Homepage
Repository (GitHub)
View/report issues

Topics

#stepper #wizard #onboarding #widget

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on awesome_stepper