UI
Build custom checklist and survey components styled to your app's design system.
By default, sunboard init wires the built-in surfaces: <Sunboard.Checklist />
(a generic floating widget) and <Sunboard.Survey /> (a centered modal that
shows every question at once). /sunboard.ui replaces either or both with
custom components that match your app's design system, mounted in the runtime
wrapper. The useChecklist() and useSurvey() hooks own the data; the skill
owns the visual layer.
When to use it
- The default widgets don't match your look.
- You want the checklist somewhere specific — a sidebar panel, a home-page card, a nav dropdown.
- You want onboarding to read as a full "set up your account" page rather than a popup to dismiss.
What happens
- Inventories your design system once — Tailwind, shadcn, MUI, CSS modules, your button/radio/card/progress primitives, your icon library, and how dark mode works — then builds every surface from the same inventory, so the components come out looking like siblings.
- Builds the checklist component at
components/sunboard/onboarding-checklist.tsx, rendering fromuseChecklist()with your own primitives, including the completion screen when the spec defines one. It asks where the checklist should live rather than assuming the floating-corner shape. - Builds the survey component at
components/sunboard/onboarding-survey.tsx— by default a dedicated/onboardingpage with one question (or a small group) per step, a progress bar, and Back/Continue driven byuseSurvey(). A modal or single-screen layout if you prefer. - Mounts them in
sunboard-provider.tsx, replacing the built-ins (tours, tooltips, and hotspots stay as they are). - Deploys to the sandbox so you see the result against your real app on the
pk_test_key.
It builds only the surfaces you use — restyling the checklist never requires touching the survey, and vice versa.
The hook contracts
Everything you render comes from the hooks — copy is already token-interpolated, so you never re-interpolate:
const {
visible, // render nothing when false
title, // user-facing heading (pre-interpolated)
subtitle, // optional eyebrow
steps, // ChecklistViewStep[]
progress, // 0..100
isCompleted, // (key) => boolean
startStep, // (step) => void — handles navigation + tour launch
completeStep, // (step) => void — the user checks the step off
completion, // success-screen content, when present
dismissCompletion // () => void
} = useChecklist();const {
visible, // owed + not yet submitted — render nothing when false
title, // pre-interpolated heading
steps, // SurveyViewStep[] — authored groups, or one per question
currentStep, // render this step's questions
progress, // 0..100, by step
isLastStep,
canAdvance, // current step's required questions answered
next, // advance, or submit on the last step
back,
answers, // Record<string, unknown>
setAnswer, // (key, value) — single-select / text
toggleAnswer, // (key, value) — one value of a multi-select
submit, // posts answers, then re-bootstraps
} = useSurvey();Don't reach past the hooks
If you find yourself reading step.action.type or step.route, stop — call
startStep(step) and let the runtime decide what to do. Render the survey from
currentStep and let next/back/progress drive the flow. The hooks' field
names are a stable, append-only contract. For imperative work like track(),
use useSunboard() separately.