Experience Pages Reference
Every page below ships with sensible defaults, named slots for customization, and a context prop that's auto-injected by <ExperienceFlow>. All pages emit next, back, and (where relevant) exit. See the Overview for the wiring model.
Slot conventions:
header,body,actions— common structural slots.default— when set, replaces the entire page content. Use for full custom rendering.- All action slots receive scoped props like
:next,:back,:submit, so you can keep behavior while replacing markup.
Default action / result key:
Built-in pages declare these via withExperienceDefaults. The flow applies them unless you override on the page def.
Intro & legal
WelcomePage
Splash / landing screen.
import { WelcomePage } from '@gxp-dev/uikit'
| Prop | Type | Default | Notes |
|---|---|---|---|
title | string | 'Welcome' | |
subtitle | string | — | |
ctaText | string | 'Get Started' | |
backgroundImage | string | — | Optional background URL. |
Emits: next, exit
Slots: default, header, body, actions({ next })
Default action: none.
TermsPage
Legal / consent page with optional accept checkbox.
import { TermsPage } from '@gxp-dev/uikit'
| Prop | Type | Default |
|---|---|---|
title | string | 'Terms & Conditions' |
html | string | — (rendered with v-html, sanitise upstream) |
requireAccept | boolean | true |
checkboxLabel | string | 'I have read and agree to the terms.' |
acceptLabel | string | 'Accept' |
declineLabel | string | 'Back' |
Emits: next({ accepted: true }), back, exit
Slots: header, body, actions({ accept, back, canContinue })
Default action: none. Result key: termsAccepted.
InstructionsPage
How-to guide with optional image or video.
| Prop | Type | Default |
|---|---|---|
title | string | 'How it works' |
html | string | — |
mediaSrc | string | — |
mediaType | 'image' | 'video' | auto-detected from URL |
ctaText | string | 'Continue' |
backLabel | string | 'Back' |
showBack | boolean | true |
Emits: next, back, exit
Slots: header, media, body, actions({ next, back })
Default action: none.
Capture
CameraPage
Photo capture with optional countdown, mirrored preview, border/silhouette overlays. Uses useMedia internally.
| Prop | Type | Default |
|---|---|---|
countdown | number | 3 |
facingMode | 'user' | 'environment' | 'user' |
mirrored | boolean | true |
aspectRatio | number | null | null |
borderSrc | string | — |
silhouetteSrc | string | — |
captureLabel | string | 'Take photo' |
Emits: next(blob: Blob), back, exit
Slots: overlay({ counter }), controls({ capture, back, isCapturing })
Default action: none. Result key: photoBlob.
CameraReviewPage
Photo preview with accept / retake (and optional caption).
| Prop | Type | Default |
|---|---|---|
photoBlob | Blob | null | reads ctx.photoBlob if not set |
allowCaption | boolean | false |
captionPlaceholder | string | 'Add a caption…' |
acceptLabel | string | 'Use photo' |
retakeLabel | string | 'Retake' |
Emits: next({ blob: Blob, caption?: string }), back, exit
Slots: preview({ url, blob }), caption, actions({ accept, retake })
Default action: 'publishPost'. Result key: post.
VideoCapturePage
Video recording (MediaRecorder via useMedia) with max-duration timer.
| Prop | Type | Default |
|---|---|---|
maxDurationSeconds | number | 30 |
facingMode | 'user' | 'environment' | 'user' |
mirrored | boolean | true |
aspectRatio | number | null | null |
recordLabel/stopLabel | string | 'Record'/'Stop' |
Emits: next(blob: Blob), back, exit
Slots: overlay({ elapsed, remaining, isRecording }), controls({ start, stop, isRecording })
Default action: none. Result key: videoBlob.
VideoReviewPage
Plays back the captured video with accept / retake. Mirror image of CameraReviewPage.
| Prop / behavior | Same as CameraReviewPage but reads ctx.videoBlob and renders a <video controls> |
Emits: next({ blob, caption? }), back, exit
Default action: 'publishPost'. Result key: post.
AudioCapturePage
Audio-only recording with frequency visualizer + timer.
| Prop | Type | Default |
|---|---|---|
maxDurationSeconds | number | 30 |
title | string | 'Record audio' |
recordLabel | string | 'Record' |
stopLabel | string | 'Stop' |
Emits: next(blob: Blob), back, exit
Slots: header, controls({ start, stop, isRecording })
Default action: none. Result key: audioBlob.
AudioReviewPage
Audio playback + accept / retake.
Emits: next({ blob, caption? }), back, exit
Default action: 'publishPost'. Result key: post.
DrawingPage
Canvas drawing with pen colors, eraser, background colors, undo, and clear.
| Prop | Type | Default |
|---|---|---|
width/height | number | 1024 |
brushSize | number | 8 |
penColors | string[] | ['#000000', '#ef4444', ...] |
backgroundColors | string[] | ['#ffffff', '#fde68a', ...] |
initialPenColor | string | '#000000' |
initialBackgroundColor | string | '#ffffff' |
submitLabel | string | 'Done' |
Emits: next(blob: Blob), back, exit
Slots: toolbar({ tool, setTool, penColor, backgroundColor, undo, clear }), actions({ submit, back })
Default action: none. Result key: drawingBlob.
NotepadPage
Typed-text note with selectable pen + paper colors. Renders to a PNG blob on submit.
| Prop | Type | Default |
|---|---|---|
title | string | 'Leave a note' |
maxCharacters | number | 280 |
penColors | string[] | default palette |
backgroundColors | string[] | default palette |
renderWidth/renderHeight | number | 1024 |
Emits: next({ blob: Blob, caption: string }), back, exit
Default action: none. Result key: note.
TextPage
Multiline text input with character limit.
| Prop | Type | Default |
|---|---|---|
title | string | 'Share your thoughts' |
placeholder | string | 'Type here…' |
characterLimit | number | 500 |
rows | number | 6 |
initialValue | string | '' |
Emits: next(text: string), back, exit
Default action: none. Result key: text.
PromptPage
Freeform or Mad-Libs prompt entry. When template + tokens are provided, renders the Mad-Libs UI; otherwise renders a textarea.
| Prop | Type | Default |
|---|---|---|
title | string | 'Describe what you want' |
template | string | — (e.g. 'A {animal} on a {vehicle}') |
tokens | PromptTemplateToken[] | [] |
placeholder | string | 'Enter your prompt…' |
interface PromptTemplateToken {
key: string
label?: string
options: string[]
}
Emits: next(prompt: string), back, exit
Default action: none. Result key: prompt.
OptionsPage
Choice-button grid. Useful for branching flows (see Overview → Branching paths).
| Prop | Type | Default |
|---|---|---|
title | string | 'Choose one' |
options | ExperienceOption[] | [] |
emitKeyOnly | boolean | false |
interface ExperienceOption {
key: string
label: string
icon?: string
data?: unknown
}
Emits: next(option | option.key), back, exit
Default action: none. Result key: choice.
FormPage
Dynamic schema-driven form (text/email/textarea/select/radio/checkbox/heading) with required-field gating and when() field visibility.
interface FormFieldDef {
type: 'text' | 'email' | 'textarea' | 'select' | 'radio' | 'checkbox' | 'heading'
name: string
label?: string
placeholder?: string
required?: boolean
options?: { value: string; label: string }[]
defaultValue?: string | boolean
when?: (values: Record<string, unknown>) => boolean
}
| Prop | Type | Default |
|---|---|---|
title | string | — |
subtitle | string | — |
fields | FormFieldDef[] | [] |
submitLabel | string | 'Submit' |
Emits: next(values: Record<string, unknown>), back, exit
Default action: none. Result key: formValues.
Outcome
ResultsGalleryPage
Gallery of generated results with selection + optional regenerate.
| Prop | Type | Default |
|---|---|---|
title | string | 'Pick a result' |
results | GalleryResult[] | reads ctx[contextKey] if empty |
contextKey | string | 'results' |
selectLabel | string | 'Use this one' |
showRegenerate | boolean | false |
interface GalleryResult {
id: string | number
imageUrl: string
label?: string
data?: unknown
}
Emits: next(result), regenerate, back, exit
Default action: none. Result key: selectedResult.
PhotoEditPage
Canvas compositor: pick a subject, cycle through backdrops, optional border overlay. Renders the composite to a PNG blob on submit.
| Prop | Type | Default |
|---|---|---|
title | string | 'Edit your photo' |
subjects | SubjectOption[] | [] |
backdrops | BackdropOption[] | [] |
borderUrl | string | — |
outputWidth/outputHeight | number | 1024 |
interface SubjectOption { key: string; label?: string; imageUrl: string }
interface BackdropOption { key: string; label?: string; imageUrl: string }
Emits: next(blob: Blob), back, exit
Default action: none. Result key: editedBlob.
SharePage
Final media display with attribution, native navigator.share, and fallback modal.
interface SharePageMedia {
url: string
type: 'image' | 'video' | 'audio'
caption?: string
}
interface ShareAttribution { name?: string; avatarUrl?: string }
| Prop | Type | Default |
|---|---|---|
title | string | 'Share' |
media | SharePageMedia | reads ctx.post.{file_url,type} |
attribution | ShareAttribution | — |
shareLabel | string | 'Share' |
doneLabel | string | 'Done' |
Emits: next, back, exit, shared(success: boolean)
Slots: header, media, actions({ share, done }), fallback({ visible, dismiss })
Default action: none.
FinalPage
Outcome screen with optional QR code and configurable action buttons.
interface FinalActionDescriptor {
key: string
label: string
variant?: 'primary' | 'secondary' | 'ghost'
}
| Prop | Type | Default |
|---|---|---|
title | string | 'All done!' |
imageUrl | string | reads ctx.post.file_url |
qrUrl | string | — |
qrCaption | string | — |
actions | FinalActionDescriptor[] | [] |
restartLabel | string | 'Start over' |
showRestart | boolean | true |
Emits: next (restart), action(key: string), exit
Slots: header, result, qr, actions({ restart, run })
Default action: none.
Utility
LoadingPage
Standalone loading page for long-running async waits between steps (separate from the automatic flow.busy overlay). Use when you want a dedicated screen — e.g. polling for AI image generation.
type LoadingTask<TResult = unknown> = (context: Record<string, unknown>) => Promise<TResult>
| Prop | Type | Default |
|---|---|---|
messages | string[] | ['Working on it…'] |
messageInterval | number | 2.5 (seconds) |
timeoutSeconds | number | 0 (no timeout) |
task | LoadingTask | — |
When task resolves the page emits next(result); on rejection it emits error(err).
Default action: false (the page's own task produces the result; the flow's action would double-handle).
At a glance
| Page | Default action | Result key |
|---|---|---|
WelcomePage | — | welcome |
TermsPage | — | termsAccepted |
InstructionsPage | — | instructions |
CameraPage | — | photoBlob |
CameraReviewPage | publishPost | post |
VideoCapturePage | — | videoBlob |
VideoReviewPage | publishPost | post |
AudioCapturePage | — | audioBlob |
AudioReviewPage | publishPost | post |
DrawingPage | — | drawingBlob |
NotepadPage | — | note |
TextPage | — | text |
PromptPage | — | prompt |
OptionsPage | — | choice |
FormPage | — | formValues |
ResultsGalleryPage | — | selectedResult |
PhotoEditPage | — | editedBlob |
SharePage | — | share |
LoadingPage | false | loadingResult |
FinalPage | — | final |
To change a default, set action and/or resultKey in the page def.