Modern Questionnaire Form
A 3-step questionnaire wizard (a progress bar, single-select cards with descriptions, multi-select checkboxes, and a plain single-select list) with Previous/Skip/Next/Save actions. Inspired by the shadcn/ui QuestionnaireDemo composition.
No settings required
This component works out of the box — no configuration needed.
How to Use
- No settings: this component needs none. Every question's title, description, and choices are fixed in the YAML; edit each step's galQFormChoices Items array directly in the pasted YAML to relabel, add, or remove choices (also update that Gallery's Height/TemplateSize and the step container's own Height to match if the item count changes).
- Paste the YAML into Power Apps Studio (Ctrl+V). One GroupContainer (conQFormCard) is added. All 3 steps live inside it, stacked via Vertical AutoLayout, only one Visible at a time.
- A single variable, varQFormStep (1-3), tracks the current step: every reference to it goes through Coalesce(varQFormStep, 1), so no OnVisible initialisation is needed; the form always starts on step 1. The thin progress bar at the top (imgQFormProgressFill) is a plain colored Image whose Width is (track width) × (step ÷ 3).
- Step 1 (single-select, required) and step 3 (single-select, required) both use Choice Card's proven row pattern: an always-visible border ring (dark when selected, light grey at rest) on a topmost, permanently transparent hit-target button. Title/description are separate Labels underneath it, so the button can never obscure them, in any state. Their answers live in varQFormAnswer1 / varQFormAnswer3.
- Step 2 (multi-select, optional) uses a small bordered GroupContainer (conQFC2Box) as a checkbox indicator instead of the Image+Blank() "colored shape" trick used elsewhere in this project. That trick produces a borderless shape, which would be invisible at rest against the white card. Selected values live in a Collection, colQFormSignals ({Value:...} rows), toggled via the same Collect/Remove idiom as DropDown Multiple.
- Next validates only on step 1 (blocks advancing if neither a choice NOR the free-text input has anything, showing conQFormErrorWrap's message). Step 2 has no validation since it's optional, so Skip and Next behave identically there. Save validates step 3 the same way.
- Save (btnQFormSubmit) builds a single-row Collection, colQFormAnswers ({Goal, Features, Timeline}), from all 3 answers: Goal is Coalesce(varQFormAnswer1, inputQFormOther.Text) (a choice if picked, else the free-text), Features is colQFormSignals joined into one comma-separated string via Concat(..., Value, ", "), Timeline is varQFormAnswer3, then shows a placeholder confirmation toast (Notify()). ClearCollect (not Collect) so re-clicking Save always replaces the one row rather than accumulating duplicates.
- This is the first multi-step/wizard component in this project. Test the step transitions and the fixed-height/shorter-step blank-space trade-off carefully in Studio before relying on it as a template for further wizards.
Problem Solved
Provides a ready-made multi-step questionnaire/wizard (progress indicator, mixed single/multi-select question types, inline validation, and a single consolidated answers Collection on submit) which Power Apps has no native equivalent for and would otherwise require hand-building step logic from scratch.
Integration
colQFormAnswers (built on Save) is the ready-to-use output: read colQFormAnswers.Goal / .Features / .Timeline (or First(colQFormAnswers) for the whole row) to wire into your real submit/save action, exactly like the source's own FormData read (direction/signals/timing).
YAML Code
- conQFormCard:
Control: GroupContainer@1.5.0
Variant: AutoLayout
Properties:
BorderColor: =RGBA(226, 232, 240, 1)
BorderThickness: =1
DropShadow: =DropShadow.None
Fill: =RGBA(255, 255, 255, 1)
Height: =If(conQFormProgressWrap.Visible, conQFormProgressWrap.Height,0) + If(conQFormStep1.Visible, conQFormStep1.Height,0)+ If(conQFormStep2.Visible, conQFormStep2.Height,0)+ If(conQFormStep3.Visible, conQFormStep3.Height,0)+ If(conQFormErrorWrap.Visible, conQFormErrorWrap.Height,0)+ If(conQFormActions.Visible, conQFormActions.Height,0)
LayoutAlignItems: =LayoutAlignItems.Stretch
LayoutDirection: =LayoutDirection.Vertical
RadiusBottomLeft: =8
RadiusBottomRight: =8
RadiusTopLeft: =8
RadiusTopRight: =8
Width: =360
X: =20
Y: =20
Children:
- conQFormProgressWrap:
Control: GroupContainer@1.5.0
Variant: ManualLayout
Properties:
DropShadow: =DropShadow.None
FillPortions: =0
Height: =36
LayoutMinHeight: =0
LayoutMinWidth: =0
Children:
- imgQFormProgressTrack:
Control: Image@2.2.3
Properties:
Fill: =RGBA(226, 232, 240, 1)
Height: =4
Image: =Blank()
Width: =Parent.Width - 32
X: =16
Y: =16
- imgQFormProgressFill:
Control: Image@2.2.3
Properties:
Fill: =RGBA(15, 23, 42, 1)
Height: =4
Image: =Blank()
RadiusBottomLeft: =2
RadiusBottomRight: =2
RadiusTopLeft: =2
RadiusTopRight: =2
Width: =(Parent.Width - 32) * (Coalesce(varQFormStep, 1) / 3)
X: =16
Y: =16
- conQFormStep1:
Control: GroupContainer@1.5.0
Variant: AutoLayout
Properties:
DropShadow: =DropShadow.None
FillPortions: =0
Height: =330
LayoutAlignItems: =LayoutAlignItems.Stretch
LayoutDirection: =LayoutDirection.Vertical
LayoutGap: =8
LayoutMinHeight: =0
LayoutMinWidth: =0
PaddingBottom: =10
PaddingLeft: =16
PaddingRight: =16
PaddingTop: =10
Visible: =(Coalesce(varQFormStep, 1) = 1)
Children:
- lblQFormTitle1:
Control: Label@2.5.1
Properties:
AlignInContainer: =AlignInContainer.Stretch
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
FontWeight: =FontWeight.Semibold
LayoutMinHeight: =0
LayoutMinWidth: =0
Size: =12
Text: ="What's your main goal with this workspace?"
- lblQFormDesc1:
Control: Label@2.5.1
Properties:
AlignInContainer: =AlignInContainer.Stretch
Color: =RGBA(100, 116, 139, 1)
Font: =Font.'Open Sans'
Height: =14
LayoutMinHeight: =0
LayoutMinWidth: =0
Size: =10
Text: ="Choose a goal or describe another one."
- galQFormChoices1:
Control: Gallery@2.15.0
Variant: Vertical
Properties:
DelayItemLoading: =false
FillPortions: =0
Height: =200
Items: |-
=[
{
Label: "Team collaboration",
Description: "Share files and work together in real time.",
Value: "collaboration"
},
{
Label: "Personal organization",
Description: "Keep your own tasks and notes in one place.",
Value: "organization"
},
{
Label: "Client management",
Description: "Track client projects and communications.",
Value: "clients"
}
]
LayoutMinHeight: =0
LayoutMinWidth: =0
ShowScrollbar: =false
TemplatePadding: =0
TemplateSize: =65
Children:
- lblQFC1Title:
Control: Label@2.5.1
Properties:
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
FontWeight: =FontWeight.Semibold
Height: =18
Size: =10
Text: =ThisItem.Label
Width: =Parent.TemplateWidth - 24
X: =12
Y: =8
- lblQFC1Desc:
Control: Label@2.5.1
Properties:
Color: =RGBA(100, 116, 139, 1)
Font: =Font.'Open Sans'
Height: =30
Size: =10
Text: =ThisItem.Description
Width: =Parent.TemplateWidth - 24
X: =12
Y: =26
- btnQFC1Hit:
Control: Classic/Button@2.2.0
Properties:
BorderColor: =If(ThisItem.Value = Coalesce(varQFormAnswer1, ""), RGBA(15, 23, 42, 1), RGBA(226, 232, 240, 1))
BorderThickness: =1
Color: =RGBA(0, 0, 0, 0)
Fill: =RGBA(0, 0, 0, 0)
FocusedBorderThickness: =1
Font: =Font.'Open Sans'
Height: =Parent.TemplateHeight - 5
HoverBorderColor: =RGBA(148, 163, 184, 1)
HoverColor: =RGBA(0, 0, 0, 0)
HoverFill: =RGBA(0, 0, 0, 0)
OnSelect: =Set(varQFormAnswer1, ThisItem.Value); Set(varQFormError, false)
PressedFill: =RGBA(0, 0, 0, 0)
RadiusBottomLeft: =6
RadiusBottomRight: =6
RadiusTopLeft: =6
RadiusTopRight: =6
Text: =""
Width: =Parent.TemplateWidth - 5
- inputQFormOther:
Control: Classic/TextInput@2.3.2
Properties:
AlignInContainer: =AlignInContainer.Stretch
BorderColor: =RGBA(226, 232, 240, 1)
BorderThickness: =1
Color: =RGBA(15, 23, 42, 1)
Default: =""
FocusedBorderThickness: =1
Font: =Font.'Open Sans'
Height: =32
HintText: ="Describe another goal…"
HoverFill: =Color.Transparent
LayoutMinHeight: =0
LayoutMinWidth: =0
Size: =10
- conQFormStep2:
Control: GroupContainer@1.5.0
Variant: AutoLayout
Properties:
DropShadow: =DropShadow.None
FillPortions: =0
Height: =250
LayoutAlignItems: =LayoutAlignItems.Stretch
LayoutDirection: =LayoutDirection.Vertical
LayoutGap: =8
LayoutMinHeight: =0
LayoutMinWidth: =0
PaddingBottom: =10
PaddingLeft: =16
PaddingRight: =16
PaddingTop: =10
Visible: =(Coalesce(varQFormStep, 1) = 2)
Children:
- lblQFormTitle2:
Control: Label@2.5.1
Properties:
AlignInContainer: =AlignInContainer.Stretch
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
FontWeight: =FontWeight.Semibold
LayoutMinHeight: =0
LayoutMinWidth: =0
Size: =12
Text: ="Which features matter most to you?"
- lblQFormDesc2:
Control: Label@2.5.1
Properties:
AlignInContainer: =AlignInContainer.Stretch
Color: =RGBA(100, 116, 139, 1)
Font: =Font.'Open Sans'
Height: =25
LayoutMinHeight: =0
LayoutMinWidth: =0
Size: =10
Text: ="Select all that apply, or skip this question."
- galQFormChoices2:
Control: Gallery@2.15.0
Variant: Vertical
Properties:
DelayItemLoading: =false
FillPortions: =0
Height: =144
Items: =[{Label:"Notifications",Value:"notifications"}, {Label:"Integrations",Value:"integrations"}, {Label:"Reporting",Value:"reporting"}, {Label:"Mobile app",Value:"mobile-app"}]
LayoutMinHeight: =0
LayoutMinWidth: =0
ShowScrollbar: =false
TemplatePadding: =0
TemplateSize: =36
Children:
- conQFC2Box:
Control: GroupContainer@1.5.0
Variant: ManualLayout
Properties:
BorderColor: =If(ThisItem.Value in colQFormSignals.Value, RGBA(15, 23, 42, 1), RGBA(203, 213, 225, 1))
BorderThickness: =1
DropShadow: =DropShadow.None
Fill: =If(ThisItem.Value in colQFormSignals.Value, RGBA(15, 23, 42, 1), RGBA(255, 255, 255, 1))
Height: =14
RadiusBottomLeft: =3
RadiusBottomRight: =3
RadiusTopLeft: =3
RadiusTopRight: =3
Width: =14
X: =12
Y: =11
- lblQFC2Label:
Control: Label@2.5.1
Properties:
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
Height: =16
Size: =10
Text: =ThisItem.Label
Width: =Parent.TemplateWidth - 46
X: =34
Y: =10
- btnQFC2Hit:
Control: Classic/Button@2.2.0
Properties:
BorderColor: =RGBA(0, 0, 0, 0)
BorderThickness: =1
Color: =RGBA(0, 0, 0, 0)
Fill: =RGBA(0, 0, 0, 0)
FocusedBorderThickness: =0
Font: =Font.'Open Sans'
Height: =Parent.TemplateHeight
HoverBorderColor: =RGBA(226, 232, 240, 1)
HoverColor: =RGBA(0, 0, 0, 0)
HoverFill: =RGBA(0, 0, 0, 0)
OnSelect: |-
=If(ThisItem.Value in colQFormSignals.Value, Remove(colQFormSignals, LookUp(colQFormSignals, Value = ThisItem.Value)), Collect(colQFormSignals, {Value: ThisItem.Value}))
PressedFill: =RGBA(0, 0, 0, 0)
RadiusBottomLeft: =6
RadiusBottomRight: =6
RadiusTopLeft: =6
RadiusTopRight: =6
Text: =""
Width: =Parent.TemplateWidth
- conQFormStep3:
Control: GroupContainer@1.5.0
Variant: AutoLayout
Properties:
DropShadow: =DropShadow.None
FillPortions: =0
Height: =188
LayoutAlignItems: =LayoutAlignItems.Stretch
LayoutDirection: =LayoutDirection.Vertical
LayoutGap: =8
LayoutMinHeight: =0
LayoutMinWidth: =0
PaddingBottom: =10
PaddingLeft: =16
PaddingRight: =16
PaddingTop: =10
Visible: =(Coalesce(varQFormStep, 1) = 3)
Children:
- lblQFormTitle3:
Control: Label@2.5.1
Properties:
AlignInContainer: =AlignInContainer.Stretch
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
FontWeight: =FontWeight.Semibold
Height: =18
LayoutMinHeight: =0
LayoutMinWidth: =0
Size: =12
Text: ="How soon do you plan to get started?"
- lblQFormDesc3:
Control: Label@2.5.1
Properties:
AlignInContainer: =AlignInContainer.Stretch
Color: =RGBA(100, 116, 139, 1)
Font: =Font.'Open Sans'
Height: =25
LayoutMinHeight: =0
LayoutMinWidth: =0
Size: =10
Text: ="Choose the timeline that fits best."
- galQFormChoices3:
Control: Gallery@2.15.0
Variant: Vertical
Properties:
DelayItemLoading: =false
FillPortions: =0
Height: =108
Items: =[{Label:"Right away",Value:"now"}, {Label:"Within a month",Value:"soon"}, {Label:"Just exploring",Value:"exploring"}]
LayoutMinHeight: =0
LayoutMinWidth: =0
ShowScrollbar: =false
TemplatePadding: =0
TemplateSize: =36
Children:
- lblQFC3Label:
Control: Label@2.5.1
Properties:
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
Height: =25
Size: =10
Text: =ThisItem.Label
Width: =Parent.TemplateWidth - 24
X: =12
Y: =5
- btnQFC3Hit:
Control: Classic/Button@2.2.0
Properties:
BorderColor: =If(ThisItem.Value = Coalesce(varQFormAnswer3, ""), RGBA(15, 23, 42, 1), RGBA(226, 232, 240, 1))
BorderThickness: =1
Color: =RGBA(0, 0, 0, 0)
Fill: =RGBA(0, 0, 0, 0)
FocusedBorderThickness: =1
Font: =Font.'Open Sans'
Height: =Parent.TemplateHeight - 5
HoverBorderColor: =RGBA(148, 163, 184, 1)
HoverColor: =RGBA(0, 0, 0, 0)
HoverFill: =RGBA(0, 0, 0, 0)
OnSelect: =Set(varQFormAnswer3, ThisItem.Value); Set(varQFormError, false)
PressedFill: =RGBA(0, 0, 0, 0)
RadiusBottomLeft: =6
RadiusBottomRight: =6
RadiusTopLeft: =6
RadiusTopRight: =6
Text: =""
Width: =Parent.TemplateWidth - 5
- conQFormErrorWrap:
Control: GroupContainer@1.5.0
Variant: AutoLayout
Properties:
DropShadow: =DropShadow.None
FillPortions: =0
Height: =20
LayoutAlignItems: =LayoutAlignItems.Stretch
LayoutDirection: =LayoutDirection.Vertical
LayoutMinHeight: =0
LayoutMinWidth: =0
PaddingLeft: =16
PaddingRight: =16
Visible: =Coalesce(varQFormError, false)
Children:
- lblQFormError:
Control: Label@2.5.1
Properties:
AlignInContainer: =AlignInContainer.Stretch
Color: =RGBA(220, 38, 38, 1)
Font: =Font.'Open Sans'
Height: =20
LayoutMinHeight: =0
LayoutMinWidth: =0
Size: =10
Text: ="Please select an option to continue."
- conQFormActions:
Control: GroupContainer@1.5.0
Variant: AutoLayout
Properties:
DropShadow: =DropShadow.None
FillPortions: =0
Height: =64
LayoutAlignItems: =LayoutAlignItems.Stretch
LayoutDirection: =LayoutDirection.Horizontal
LayoutGap: =8
LayoutMinHeight: =0
LayoutMinWidth: =0
PaddingBottom: =16
PaddingLeft: =16
PaddingRight: =16
PaddingTop: =16
Children:
- btnQFormPrev:
Control: Classic/Button@2.2.0
Properties:
AlignInContainer: =AlignInContainer.Stretch
BorderColor: =RGBA(226, 232, 240, 1)
BorderThickness: =1
Color: =RGBA(15, 23, 42, 1)
Fill: =RGBA(0, 0, 0, 0)
FocusedBorderThickness: =0
Font: =Font.'Open Sans'
HoverBorderColor: =RGBA(148, 163, 184, 1)
HoverColor: =Self.Color
HoverFill: =RGBA(241, 245, 249, 1)
LayoutMinHeight: =0
LayoutMinWidth: =0
OnSelect: =Set(varQFormStep, Max(1, Coalesce(varQFormStep, 1) - 1)); Set(varQFormError, false)
PressedFill: =RGBA(226, 232, 240, 1)
RadiusBottomLeft: =6
RadiusBottomRight: =6
RadiusTopLeft: =6
RadiusTopRight: =6
Size: =10
Text: ="Previous"
Visible: =Coalesce(varQFormStep, 1) > 1
Width: =80
- conQFormActionsSpacer:
Control: GroupContainer@1.5.0
Variant: ManualLayout
Properties:
DropShadow: =DropShadow.None
LayoutMinHeight: =0
LayoutMinWidth: =0
- btnQFormSkip:
Control: Classic/Button@2.2.0
Properties:
AlignInContainer: =AlignInContainer.Stretch
BorderColor: =RGBA(0, 0, 0, 0)
BorderThickness: =1
Color: =RGBA(100, 116, 139, 1)
Fill: =RGBA(0, 0, 0, 0)
FocusedBorderThickness: =0
Font: =Font.'Open Sans'
HoverBorderColor: =RGBA(0, 0, 0, 0)
HoverColor: =Self.Color
HoverFill: =RGBA(241, 245, 249, 1)
LayoutMinHeight: =0
LayoutMinWidth: =0
OnSelect: =Set(varQFormStep, Min(3, Coalesce(varQFormStep, 1) + 1)); Set(varQFormError, false)
PressedFill: =RGBA(226, 232, 240, 1)
RadiusBottomLeft: =6
RadiusBottomRight: =6
RadiusTopLeft: =6
RadiusTopRight: =6
Size: =10
Text: ="Skip"
Visible: =Coalesce(varQFormStep, 1) = 2
Width: =60
- btnQFormNext:
Control: Classic/Button@2.2.0
Properties:
AlignInContainer: =AlignInContainer.Stretch
BorderColor: =RGBA(0, 0, 0, 0)
BorderThickness: =1
Color: =RGBA(255, 255, 255, 1)
Fill: =RGBA(15, 23, 42, 1)
FocusedBorderThickness: =0
Font: =Font.'Open Sans'
HoverBorderColor: =RGBA(0, 0, 0, 0)
HoverColor: =Self.Color
HoverFill: =RGBA(51, 65, 85, 1)
LayoutMinHeight: =0
LayoutMinWidth: =0
OnSelect: =If(Coalesce(varQFormStep, 1) = 1 && IsBlank(varQFormAnswer1) && IsBlank(inputQFormOther.Text), Set(varQFormError, true), Set(varQFormStep, Min(3, Coalesce(varQFormStep, 1) + 1)); Set(varQFormError, false))
PressedFill: =RGBA(30, 41, 59, 1)
RadiusBottomLeft: =6
RadiusBottomRight: =6
RadiusTopLeft: =6
RadiusTopRight: =6
Size: =10
Text: ="Next"
Visible: =Coalesce(varQFormStep, 1) < 3
Width: =60
- btnQFormSubmit:
Control: Classic/Button@2.2.0
Properties:
AlignInContainer: =AlignInContainer.Stretch
BorderColor: =RGBA(0, 0, 0, 0)
BorderThickness: =1
Color: =RGBA(255, 255, 255, 1)
Fill: =RGBA(15, 23, 42, 1)
FocusedBorderThickness: =0
Font: =Font.'Open Sans'
HoverBorderColor: =RGBA(0, 0, 0, 0)
HoverColor: =Self.Color
HoverFill: =RGBA(51, 65, 85, 1)
LayoutMinHeight: =0
LayoutMinWidth: =0
OnSelect: |-
=If(IsBlank(varQFormAnswer3), Set(varQFormError, true), ClearCollect(colQFormAnswers, {Goal: Coalesce(varQFormAnswer1, inputQFormOther.Text), Features: Concat(colQFormSignals, Value, ", "), Timeline: varQFormAnswer3}); Notify("Preferences saved — wire this to your real submit action once connected.", NotificationType.Success))
PressedFill: =RGBA(30, 41, 59, 1)
RadiusBottomLeft: =6
RadiusBottomRight: =6
RadiusTopLeft: =6
RadiusTopRight: =6
Size: =10
Text: ="Finish"
Visible: =Coalesce(varQFormStep, 1) = 3
Width: =90Implementation
Copies the customized component code directly to your clipboard. Use Ctrl+V in Power Apps Studio.
Something not pasting right? Troubleshooting guide