Skip to main content
Version: v1 (Current)

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.


WelcomePage

Splash / landing screen.

import { WelcomePage } from '@gxp-dev/uikit'
PropTypeDefaultNotes
titlestring'Welcome'
subtitlestring
ctaTextstring'Get Started'
backgroundImagestringOptional 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'
PropTypeDefault
titlestring'Terms & Conditions'
htmlstring— (rendered with v-html, sanitise upstream)
requireAcceptbooleantrue
checkboxLabelstring'I have read and agree to the terms.'
acceptLabelstring'Accept'
declineLabelstring'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.

PropTypeDefault
titlestring'How it works'
htmlstring
mediaSrcstring
mediaType'image' | 'video'auto-detected from URL
ctaTextstring'Continue'
backLabelstring'Back'
showBackbooleantrue

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.

PropTypeDefault
countdownnumber3
facingMode'user' | 'environment''user'
mirroredbooleantrue
aspectRationumber | nullnull
borderSrcstring
silhouetteSrcstring
captureLabelstring'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).

PropTypeDefault
photoBlobBlob | nullreads ctx.photoBlob if not set
allowCaptionbooleanfalse
captionPlaceholderstring'Add a caption…'
acceptLabelstring'Use photo'
retakeLabelstring'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.

PropTypeDefault
maxDurationSecondsnumber30
facingMode'user' | 'environment''user'
mirroredbooleantrue
aspectRationumber | nullnull
recordLabel/stopLabelstring'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.

PropTypeDefault
maxDurationSecondsnumber30
titlestring'Record audio'
recordLabelstring'Record'
stopLabelstring'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.

PropTypeDefault
width/heightnumber1024
brushSizenumber8
penColorsstring[]['#000000', '#ef4444', ...]
backgroundColorsstring[]['#ffffff', '#fde68a', ...]
initialPenColorstring'#000000'
initialBackgroundColorstring'#ffffff'
submitLabelstring'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.

PropTypeDefault
titlestring'Leave a note'
maxCharactersnumber280
penColorsstring[]default palette
backgroundColorsstring[]default palette
renderWidth/renderHeightnumber1024

Emits: next({ blob: Blob, caption: string }), back, exit

Default action: none. Result key: note.


TextPage

Multiline text input with character limit.

PropTypeDefault
titlestring'Share your thoughts'
placeholderstring'Type here…'
characterLimitnumber500
rowsnumber6
initialValuestring''

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.

PropTypeDefault
titlestring'Describe what you want'
templatestring— (e.g. 'A {animal} on a {vehicle}')
tokensPromptTemplateToken[][]
placeholderstring'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).

PropTypeDefault
titlestring'Choose one'
optionsExperienceOption[][]
emitKeyOnlybooleanfalse
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
}
PropTypeDefault
titlestring
subtitlestring
fieldsFormFieldDef[][]
submitLabelstring'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.

PropTypeDefault
titlestring'Pick a result'
resultsGalleryResult[]reads ctx[contextKey] if empty
contextKeystring'results'
selectLabelstring'Use this one'
showRegeneratebooleanfalse
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.

PropTypeDefault
titlestring'Edit your photo'
subjectsSubjectOption[][]
backdropsBackdropOption[][]
borderUrlstring
outputWidth/outputHeightnumber1024
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 }
PropTypeDefault
titlestring'Share'
mediaSharePageMediareads ctx.post.{file_url,type}
attributionShareAttribution
shareLabelstring'Share'
doneLabelstring'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'
}
PropTypeDefault
titlestring'All done!'
imageUrlstringreads ctx.post.file_url
qrUrlstring
qrCaptionstring
actionsFinalActionDescriptor[][]
restartLabelstring'Start over'
showRestartbooleantrue

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>
PropTypeDefault
messagesstring[]['Working on it…']
messageIntervalnumber2.5 (seconds)
timeoutSecondsnumber0 (no timeout)
taskLoadingTask

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

PageDefault actionResult key
WelcomePagewelcome
TermsPagetermsAccepted
InstructionsPageinstructions
CameraPagephotoBlob
CameraReviewPagepublishPostpost
VideoCapturePagevideoBlob
VideoReviewPagepublishPostpost
AudioCapturePageaudioBlob
AudioReviewPagepublishPostpost
DrawingPagedrawingBlob
NotepadPagenote
TextPagetext
PromptPageprompt
OptionsPagechoice
FormPageformValues
ResultsGalleryPageselectedResult
PhotoEditPageeditedBlob
SharePageshare
LoadingPagefalseloadingResult
FinalPagefinal

To change a default, set action and/or resultKey in the page def.