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

  1. 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.
  2. Builds the checklist component at components/sunboard/onboarding-checklist.tsx, rendering from useChecklist() 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.
  3. Builds the survey component at components/sunboard/onboarding-survey.tsx — by default a dedicated /onboarding page with one question (or a small group) per step, a progress bar, and Back/Continue driven by useSurvey(). A modal or single-screen layout if you prefer.
  4. Mounts them in sunboard-provider.tsx, replacing the built-ins (tours, tooltips, and hotspots stay as they are).
  5. 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.

On this page