-
Notifications
You must be signed in to change notification settings - Fork 3
StepFlow
StepFlow is an animated, accordion-style multi-step flow component for guiding users through sequential actions — wizards, checklists, multi-step forms. Only one step is expanded at a time; completing a step auto-collapses it and advances to the next.
The component is named
StepFlow(notStepper, which is already a built-in MAUI control).
- Sequential, gated flows where step N+1 only makes sense after step N is finished (sampling, ID-checks, signing).
- Linear data-entry where you want to keep the user focused on one section at a time.
- Pages where the user can fill in fields in any order — use a regular form.
- Settings screens or open-ended lists — use ListItem.
- Tabbed parallel content — use TabView.
A StepFlow hosts a sequence of StepFlowItems. Each item has four lifecycle states:
| State | Visual | Interactive |
|---|---|---|
Disabled |
Dimmed (opacity 0.45) | No |
Active |
Expanded, full opacity | Yes |
Completed |
Collapsed, check icon, dimmed (opacity 0.78) | No (by default) |
Error |
Red ring around the indicator | Yes |
The recommended usage is controller-driven: a single StepFlowController lives on the consumer's view model and the flow is driven imperatively via CompleteCurrent, GoTo, and Reset. The controller is a plain CLR object with no MAUI dependencies, which makes it trivially unit-testable.
public class SamplingViewModel : ViewModel
{
public StepFlowController Flow { get; } = new();
public AsyncCommand ConfirmPatientCommand { get; }
public AsyncCommand FinishScanningCommand { get; }
public AsyncCommand SubmitSamplingCommand { get; }
public SamplingViewModel()
{
ConfirmPatientCommand = new AsyncCommand(() => { Flow.CompleteCurrent(); return Task.CompletedTask; });
FinishScanningCommand = new AsyncCommand(() => { Flow.CompleteCurrent(); return Task.CompletedTask; });
SubmitSamplingCommand = new AsyncCommand(() => { Flow.CompleteCurrent(); return Task.CompletedTask; });
Flow.FlowCompleted += (_, _) => { /* show snackbar etc. */ };
}
}<dui:StepFlow Controller="{Binding Flow}">
<dui:StepFlowItem Title="Confirm patient">
<dui:Button Text="Confirm" Command="{Binding ConfirmPatientCommand}" />
</dui:StepFlowItem>
<dui:StepFlowItem Title="Scan sample labels">
<!-- content -->
</dui:StepFlowItem>
<dui:StepFlowItem Title="Confirm sampling">
<dui:Button Text="Submit" Command="{Binding SubmitSamplingCommand}" />
</dui:StepFlowItem>
</dui:StepFlow>| Member | Description |
|---|---|
StepCount |
Number of steps (set by the container when items are attached). |
CurrentIndex |
Index of the currently Active step, or -1. |
States |
Read-only snapshot of every step's StepFlowItemState. |
IsCompleted |
true when every step is Completed. |
AutoAdvance |
When true (default), completing a step activates the next non-completed step. |
AutoAdvanceDelay |
Delay before auto-advance. Defaults to 800 ms. |
CompleteCurrent() |
Marks CurrentIndex Completed and (optionally) auto-advances. |
Complete(int) |
Marks the step at the given index Completed. |
GoTo(int) |
Activates the step at the given index. No-op if disabled or completed. |
Reset() |
Step 0 → Active, all others → Disabled. |
SetState(int, state) |
Explicitly set a step's state. Use sparingly. |
StepCompleted |
Raised when a step transitions to Completed. |
StepActivated |
Raised when a step transitions to Active. |
FlowCompleted |
Raised when the last step is completed. |
| Property | Description |
|---|---|
Items |
The steps, in order. XAML default ContentProperty. |
Controller |
The StepFlowController driving the flow. The container creates one automatically if you omit it. |
AllowDirectStepActivation |
When true, tapping a Disabled step card activates it. Defaults to false. |
AutoScrollIntoView |
When true (default), the closest ancestor ScrollView is scrolled so the newly active step is pinned to the top. See Auto-scroll. |
FlowCompleted |
Mirrors Controller.FlowCompleted. |
| Property | Description |
|---|---|
Title |
Header text. |
Subtitle |
Optional smaller line below the title. |
Content |
The body shown when the step is Active. XAML default content. |
IndicatorTemplate |
Optional template for the leading indicator. Defaults to a numbered/check circle. |
LockWhenCompleted |
When true (default) tapping a completed step does nothing. |
State |
Current StepFlowItemState. Driven by the container — read-only for advanced scenarios. |
Disabled, Active, Completed, Error.
The component is choreographed for a premium feel — every timing and easing is intentional:
-
Expand: animated
HeightRequestwith a smooth quartic ease-out, paired with a slightly delayed opacity fade-in and a subtle upward Y-offset settling to0. 380 ms. - Collapse: smooth cubic in/out height collapse, paired with an early opacity fade and subtle upward Y-offset. 280 ms.
- Completion indicator: the check animation fades in while the title is indented to reserve measured space for the indicator. 260 ms.
-
Lift: when an item goes
Disabled → Active, scale eases from0.985 → 1.0while opacity ramps to1.0. 300 ms. - Completed dimming: opacity fades to 0.78 over 360 ms.
All animations use a unique token per item instance, so multiple StepFlows on the same page do not interfere.
When the StepFlow lives inside a ScrollView, it automatically scrolls the newly active step to the top of the scroller as the flow advances. There is no need to subscribe to Controller.StepActivated or call ScrollToAsync from the hosting page — the component walks up the visual tree to find the closest ancestor ScrollView and uses ScrollToPosition.Start, so the freshly expanded body is fully visible rather than half-cropped at the bottom of the viewport.
The initial active step created while the controller is initialized does not auto-scroll. This keeps page navigation from jumping back to step 1 before the user has interacted with the flow.
The scroll is timed to start once the expand animation has finished, so the target rect reflects the body's measured height rather than the still-animating zero-height rect.
Set AutoScrollIntoView="False" on the StepFlow to opt out — for example when you want to handle scrolling yourself, or when the flow is not hosted inside a scroller.
<dui:ScrollView>
<dui:StepFlow Controller="{Binding Flow}" AutoScrollIntoView="True">
<!-- steps -->
</dui:StepFlow>
</dui:ScrollView>If no ancestor ScrollView is found, AutoScrollIntoView is a silent no-op. Non-MAUI scrollers (CollectionView etc.) are not supported.
If you don't want a controller you can omit the Controller property and bind State two-way on each item. The container still enforces single-active and animates correctly, but you become responsible for orchestration. Not recommended.
See the Step Flow entry in the Components sample app for a working three-step sampling flow that mirrors the Arena Mobile pattern: confirm patient → scan labels → confirm sampling, with Reset to demonstrate the imperative reset path.
Components
- Buttons
- Checkboxes
- Chip
- CollectionView
- Content control
- Context Menus
- Counters
- Divider
- Labels
- ListItem
- Pickers
- SaveView
- SortControl
- StepFlow
- Tag
- TextFields
- Toolbar
Feedback & State
Guides
- Displaying code inside a mobile app
- Layout Diagnostics
- Memory Leaks
- Performance
- Providing help in your app
Interaction & Accessibility
Layout & Navigation
Media
Styling & Resources