Tip: Interface patterns give you an opportunity to explore different interface designs. Be sure to check out How to Adapt a Pattern for Your Application.
GoalCopy link to clipboard
Create a wizard that divides a form into validated steps and uses the milestone component to display progress.
Wizards help break large forms into smaller, manageable steps, allowing users to move back and forth without losing data.
In this example, users navigate steps with milestone links or Next/Back buttons. All fields must be completed to progress, but users can revisit previous steps even if they have incomplete fields on their current step. The final step shows a read-only confirmation of their entries.
This scenario demonstrates:
- Creating a wizard using the showWhen parameter.
- Conditionally setting readOnly and required parameters.
- Using a milestone field to display the current step of the wizard.
ExpressionCopy link to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
a!localVariables(
local!employee: a!map(firstName: null, lastName: null, department: null, title: null, phoneNumber: null, startDate: null),
local!activeStep: 1,
local!steps: {"Basic Info", "Additional Info", "Review"},
a!formLayout(
label: "Example: Onboarding Wizard",
contents: {
a!columnsLayout(
columns: {
a!columnLayout(
contents: {
a!milestoneField(
steps: local!steps,
links: {
a!forEach(
items: local!steps,
expression: if(
/* This logic ensures the link appears only for previous steps. */
fv!index < local!activeStep,
a!dynamicLink(
label: fv!item,
value: fv!index,
saveInto: local!activeStep
),
null
)
)
},
active: local!activeStep,
/* Choose a different step style to easily change the appearance of the milestone */
stepStyle: "DOT",
orientation: if(
a!isPageWidth(pageWidths: { "PHONE" }),
"HORIZONTAL",
"VERTICAL"
),
marginAbove: "STANDARD",
marginBelow: "MORE"
)
},
width: "NARROW"
),
a!columnLayout(
contents: {
a!sectionLayout(
label: if(
/* This logic hides the step name at the top of the form on PHONE widths
since the step label is already displayed at the top when the screen is narrow */
a!isPageWidth(pageWidths: { "PHONE" }),
"",
/* This uses local!activeStep as the index of local!steps to display the name of the current step */
local!steps[local!activeStep]
),
labelSize: "MEDIUM",
labelColor: "STANDARD",
contents: {
a!sideBySideLayout(
items: {
a!sideBySideItem(
item: a!textField(
label: "First Name",
value: local!employee.firstName,
saveInto: local!employee.firstName,
showWhen: or(local!activeStep = { 1, 3 }),
required: not(local!activeStep = 3),
readOnly: local!activeStep = 3,
marginAbove: "STANDARD"
)
),
a!sideBySideItem(
item: a!textField(
label: "Last Name",
value: local!employee.lastName,
saveInto: local!employee.lastName,
showWhen: or(local!activeStep = { 1, 3 }),
required: not(local!activeStep = 3),
readOnly: local!activeStep = 3,
marginAbove: "STANDARD"
)
)
}
),
a!textField(
label: "Department",
value: local!employee.department,
saveInto: local!employee.department,
showWhen: or(local!activeStep = { 1, 3 }),
required: not(local!activeStep = 3),
readOnly: local!activeStep = 3,
marginAbove: "STANDARD"
),
a!textField(
label: "Title",
value: local!employee.title,
saveInto: local!employee.title,
showWhen: or(local!activeStep = { 1, 3 }),
required: not(local!activeStep = 3),
readOnly: local!activeStep = 3,
marginAbove: "STANDARD"
),
a!textField(
label: "Phone Number",
value: local!employee.phoneNumber,
saveInto: local!employee.phoneNumber,
showWhen: or(local!activeStep = { 2, 3 }),
required: not(local!activeStep = 3),
readOnly: local!activeStep = 3,
marginAbove: "STANDARD"
),
a!dateField(
label: "Start Date",
value: local!employee.startDate,
saveInto: local!employee.startDate,
showWhen: or(local!activeStep = { 2, 3 }),
required: not(local!activeStep = 3),
readOnly: local!activeStep = 3,
marginAbove: "STANDARD"
)
},
marginAbove: "STANDARD"
)
},
width: "MEDIUM_PLUS"
)
},
marginBelow: "STANDARD",
stackWhen: { "PHONE" }
)
},
buttons: a!buttonLayout(
primaryButtons: {
a!buttonWidget(
label: "Next",
value: local!activeStep + 1,
saveInto: local!activeStep,
style: "SOLID",
showWhen: or(local!activeStep = { 1, 2 }),
validate: true
),
a!buttonWidget(
label: "Onboard Employee",
submit: true,
style: "SOLID",
showWhen: local!activeStep = 3
)
},
secondaryButtons: {
a!buttonWidget(
label: "Back",
value: local!activeStep - 1,
saveInto: local!activeStep,
showWhen: or(local!activeStep = { 2, 3 })
)
}
),
formWidth: "MEDIUM"
)
)
Copy
Test it outCopy link to clipboard
- Copy and paste the expression into an interface object.
- Click Next without entering a value in any of the fields. The validation prevents you from moving to the next step.
- Enter values for all the fields, and click Next. The milestone progress moves to the next step.
- Enter values for all the fields in the next step, and click Next. The milestone field progresses to the final step and all values appear as read-only data.
Notable implementation detailsCopy link to clipboard
- The labelPosition, showWhen, required, and readOnly parameters are conditionally set based on the value of
local!activeStep
. - The "Back" and "Next" buttons update the value of
local!activeStep
. - The "Next" button enforces validation (
validate: true
) to ensure all fields are completed before advancing. - The "Back" button skips validation, allowing users to navigate to previous steps even if the current step has incomplete or invalid fields.
- The milestone component uses links to allow users to navigate to a step by clicking on it. Users can only click on previous steps.
FeedbackCopy link to clipboard
Was this page helpful?