super_stepper

A highly customizable animated stepper for Flutter. It includes useful defaults while allowing package users to replace every important visual part with builders.

Demo

Super Stepper demo

Features

  • Horizontal and vertical layouts
  • Controller-driven and controlled modes
  • Active, completed, inactive, error, loading, and disabled states
  • Built-in fade, scale, slide, and combined animations
  • Solid and dashed animated connectors
  • Custom step, label, connector, content, and transition builders
  • Tap restrictions and asynchronous navigation validation
  • Scrollable layouts and RTL support
  • Generic data on every step
  • Material 3 friendly defaults

In vertical layouts, each title and subtitle appears below its icon, and the connector remains centered on the icon's horizontal axis.

Installation

Install the package from pub.dev:

flutter pub add super_stepper

Or add it manually:

dependencies:
  super_stepper: ^0.4.0

For local package development, use a path dependency:

dependencies:
  super_stepper:
    path: ../super_stepper

Basic usage

final controller = SuperStepperController();
final stepperKey = GlobalKey<SuperStepperState<void>>();

final steps = [
  const SuperStep(
    title: 'Account',
    icon: Icon(Icons.person_outline),
    content: Text('Account form'),
  ),
  const SuperStep(
    title: 'Address',
    icon: Icon(Icons.location_on_outlined),
    content: Text('Address form'),
  ),
  const SuperStep(
    title: 'Payment',
    icon: Icon(Icons.payment_outlined),
    content: Text('Payment form'),
  ),
];

SuperStepper<void>(
  key: stepperKey,
  controller: controller,
  steps: steps,
  keepAlive: true,
  style: const SuperStepperStyle(
    glassEffect: true,
    glassBlur: 14,
    glassOpacity: 0.24,
  ),
);

Set keepAlive: true for forms to preserve text fields, selections, toggles, and other local widget state while navigating between steps.

Validated navigation that honors SuperStepperBehavior, onWillChange, and the navigation callbacks:

await stepperKey.currentState?.next();
await stepperKey.currentState?.previous();
await stepperKey.currentState?.jumpTo(2);

Update individual step states through the controller:

controller.complete(0);
controller.setError(1);
controller.setLoading(2);
controller.disable(1);
controller.reset();

React to navigation and handle the final step separately:

controller.addListener(() {
  final index = controller.currentStep;
  // Save a draft, run analytics, or update surrounding UI.
});

final isLastStep = controller.currentStep == steps.length - 1;

FilledButton(
  onPressed: isLastStep
      ? () async {
          // Submit the completed flow or navigate away.
        }
      : () => stepperKey.currentState?.next(),
  child: Text(isLastStep ? 'Finish' : 'Next'),
);

Fully customized indicator

SuperStepper<void>(
  steps: steps,
  currentStep: 1,
  stepBuilder: (context, step, state) {
    return AnimatedContainer(
      duration: const Duration(milliseconds: 300),
      width: state.isActive ? 70 : 48,
      height: 48,
      alignment: Alignment.center,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(14),
        gradient: state.isActive
            ? const LinearGradient(colors: [Colors.blue, Colors.purple])
            : null,
        color: state.isActive ? null : Colors.black12,
      ),
      child: step.icon ?? const SizedBox.shrink(),
    );
  },
  connectorBuilder: (context, state) {
    return Container(
      width: 48,
      height: 4,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(100),
        gradient: const LinearGradient(
          colors: [Colors.blue, Colors.purple],
        ),
      ),
    );
  },
);

Numbered steps

Set number when a step should display a specific number instead of an icon. If both icon and number are omitted, the package displays the automatic one-based step number.

SuperStepper<void>(
  steps: const [
    SuperStep<void>(title: 'Account', number: 1),
    SuperStep<void>(title: 'Address', number: 2),
    SuperStep<void>(title: 'Payment', number: 3),
  ],
);

Icon, image, or custom indicators

The icon property accepts any widget, including an Icon, Image, SVG, avatar, or custom design. Completed and error states work the same way.

SuperStepper<void>(
  steps: [
    const SuperStep<void>(
      title: 'Profile',
      icon: Icon(Icons.person),
    ),
    SuperStep<void>(
      title: 'Photo',
      icon: Image.asset(
        'assets/photo.png',
        width: 24,
        height: 24,
      ),
      completedIcon: const Icon(Icons.done_all),
      errorIcon: const Icon(Icons.warning_amber),
    ),
  ],
);

With keepAlive: true, fields, selections, toggles, and other local state stay mounted. The content area still uses only the active step's width and height; hidden steps no longer force it to the largest saved content size.

Form validation

SuperStepper<void>(
  controller: controller,
  steps: steps,
  onWillChange: (current, target) async {
    if (target > current) {
      return formKeys[current].currentState?.validate() ?? false;
    }
    return true;
  },
);

Connector size and position

Use width for the horizontal connector width, length for its vertical length, thickness for the line thickness, and margin for equal empty space before and after the line along the stepper axis. Horizontal connectors can be placed at the top, middle, or bottom of the step indicator. Label width does not affect connector positioning:

SuperStepper<void>(
  steps: steps,
  connectorStyle: const SuperStepperConnectorStyle(
    width: 72,
    thickness: 4,
    margin: 8,
    position: SuperConnectorPosition.top,
  ),
);

RTL

SuperStepper follows the surrounding Directionality, so it works naturally inside Arabic applications:

Directionality(
  textDirection: TextDirection.rtl,
  child: SuperStepper<void>(steps: steps),
);

License

MIT

Libraries

super_stepper