Skip to main content

Wizard code examples

Here are some examples of how we can use the the createWizard() builder function to create a workflow of pages. Code samples can be copied to clipboard or wrapped - hover over one to see the buttons.

Very simple example

This example shows the most basic functionality of the wizard - it helps put steps in order and helps you navigate between them.

Here is some sample step configuration for this:

wizard-config.ts

import { createWizard, WizardSteps } from '@krakentech/blueprint-onboarding';

export enum StepName {
Step1 = 'step1',
Step2 = 'step2',
HiddenStep = 'hiddenstep',
Step3 = 'step3',
Success = 'success',
}

export const route1 = 'page1';
export const route2 = 'page2';
export const route3 = 'page3';
export const hiddenStepRoute = 'hidden-step';
export const successRoute = 'success';

export const steps = [
{
name: StepName.Step1,
pathname: route1,
stepConfig: {
title: 'Step 1',
},
},
{
name: StepName.Step2,
pathname: route2,
stepConfig: {
title: 'Step 2',
},
},
{
name: StepName.HiddenStep,
pathname: hiddenStepRoute,
},
{
name: StepName.Step3,
pathname: route3,
stepConfig: {
title: 'Step 3',
},
},
{
name: StepName.Success,
pathname: successRoute,
},
] as const satisfies WizardSteps<StepName>;

export const simpleWizardConfig = {
storageKey: 'simple-wizard',
defaultFormValues: {},
stepNames: StepName,
};

export const {
useWizard: useSimpleWizard,
WizardProvider: SimpleWizardProvider,
} = createWizard(simpleWizardConfig);

Some notes on the above set up:

  • You can provide steps with no config if you don't want them to show up in the stepper. The stepperProps.activeStep will be 0 on those steps.

wizard-layout.tsx

Then you'll need to pass a routing function to the provider. This example uses NextJS and a pageLayout

import { SimpleWizardProvider, steps } from '@your-app/wizard-config';
import { useRouter } from 'next/router';
import { FC, PropsWithChildren, ReactElement } from 'react';

const SimpleWizardLayout: FC<PropsWithChildren> = ({ children }) => {
const router = useRouter();

return (
<SimpleWizardProvider
pathname={router.asPath?.split('?')[0]}
routingFn={router.push}
steps={steps}
>
{children}
</SimpleWizardProvider>
);
};

export const getSimpleWizardLayout = (page: ReactElement) => (
<SimpleWizardLayout>{page}</SimpleWizardLayout>
);

page-1.tsx

This is some simple page content that invokes the wizard utilities, and adds the provider via a call to getLayout (NextJS)

import { Button, Typography, Stepper } from '@krakentech/coral';
import { useSimpleWizard } from '@your-app/wizard-config';
import { getSimpleWizardLayout } from '@your-app/wizard-layout';

export const Page1 = () => {
const { toNextStep, stepperProps } = useSimpleWizard();
return (
<>
<Stepper {...stepperProps} />
<Typography variant="h1">Page 1</Typography>
<Container maxWidth={200}>
<Button fullWidth onClick={() => toNextStep()}>
Next
</Button>
</Container>
</>
);
};

Page1.getLayout = getSimpleWizardLayout;

Simple example with some form inputs

This example extends the basic wizard a little - it adds some form inputs to the pages and displays their values on the final screen.

From wizard config for form inputs

Add this to wizard-config.ts

import { createWizard, WizardSteps } from '@krakentech/blueprint-onboarding';

export const route1 = 'page1';
export const route2 = 'page2';
export const route3 = 'confirmation';

export enum StepName {
Step1 = 'step1',
Step2 = 'step2',
Confirmation = 'confirmation',
}

// pass this into SimpleFormWizardProvider
const getSteps = ({ affiliateCode }: SimpleFormValues) =>
[
{
name: StepName.Step1,
pathname: route1,
queryParams: affiliateCode
? {
affiliateCode: affiliateCode,
}
: undefined,
stepConfig: {
title: 'Step 1',
},
},
{
name: StepName.Step2,
pathname: route2,
stepConfig: {
title: 'Step 2',
},
},
{
name: StepName.Confirmation,
pathname: route3,
stepConfig: {
title: 'Confirmation',
},
},
] as const satisfies WizardSteps<StepName>;

export enum SimpleFormKey {
// Step1
Name = 'name',
AffiliateCode = 'affiliateCode',
// Step2
Email = 'email',
}

export type SimpleFormValues = {
[SimpleFormKey.Name]: string;
[SimpleFormKey.Email]: string;
[SimpleFormKey.AffiliateCode]: string;
};

const defaultFormValues: SimpleFormValues = {
// Supply
[SimpleFormKey.Name]: '',
[SimpleFormKey.Email]: '',
[SimpleFormKey.AffiliateCode]: '',
};

export const {
useWizard: useSimpleFormWizard,
WizardProvider: SimpleFormWizardProvider,
WizardFormHydrator: SimpleFormWizardFormHydrator,
} = createWizard({
storageKey: 'simple-form-sandbox',
defaultFormValues,
stepNames: StepName,
});

Then add this to the page-1.tsx template

import { Form, Formik } from 'formik';
import { Stack, Typography } from '@krakentech/coral';
import { FormikSubmitButton, FormikTextField } from '@krakentech/coral-formik';
import {
SimpleFormKey,
SimpleFormWizardFormHydrator,
useSimpleFormWizard,
} from '@your-app/wizard-config.ts';

const Page1 = ({ affiliateCode }: { affiliateCode: string }) => {
const { toNextStep, formValues, setFormValues } = useSimpleFormWizard();
return (
<Formik
initialValues={formValues}
onSubmit={async (values) => {
const newValues =
typeof affiliateCode === 'string'
? { ...values, affiliateCode }
: values;
setFormValues(newValues);
await toNextStep();
}}
>
{() => (
<Form>
<SimpleFormWizardFormHydrator />
<Stack direction="vertical" gap="xl" alignItems="center">
<Typography variant="h3" component="h1">
Step 1
</Typography>
<FormikTextField
name={SimpleFormKey.Name}
label="Name"
/>

<FormikSubmitButton>Next Step</FormikSubmitButton>
</Stack>
</Form>
)}
</Formik>
);
};

Example onboarding process

This is a fuller example for gathering customer data to onboarding them for electricity supply. If you choose the first tariff option, we add another step to collect a meter reading.

Parent steps and dynamic steps

Building on the previous two examples, here are the changes to the configuration to make the steps based on a choice in the form. These steps also use the parentName attribute to indicate that you should only see one child step of the parent. Steps which are defined as substeps, i.e., steps which have a parentName, will not appear in the stepper.

import {
WizardSteps,
} from '@krakentech/blueprint-onboarding';

...
const basicSteps = [
{
name: StepName.Supply,
pathname: supplyPathname,
stepConfig: {
title: 'Supply',
},
},
{
name: StepName.TariffSelection,
pathname: tariffSelectionPathname,
stepConfig: {
title: 'Tariff Selection',
},
},
{
name: StepName.DetailsPersonal,
pathname: personalDetailsPathname,
stepConfig: {
title: 'Details',
},
},
{
name: StepName.DetailsGeneral,
parentName: StepName.DetailsPersonal,
pathname: generalDetailsPathname,
},
{
name: StepName.DetailsPayment,
parentName: StepName.DetailsPersonal,
pathname: paymentDetailsPathname,
},
{
name: StepName.Readings,
pathname: readingsPathname,
stepConfig: {
title: 'Readings',
},
},
{
name: StepName.Confirmation,
pathname: confirmationPathname,
stepConfig: {
title: 'Confirmation',
},
},
] as const satisfies WizardSteps<StepName>;

const defaultSteps = (formValues: OnboardingFormValues) =>
[
{
name: StepName.Supply,
pathname: supplyPathname,
queryParams: formValues.affiliateCode
? {
affiliateCode: formValues.affiliateCode,
}
: undefined,
stepConfig: {
title: 'Supply',
},
},
{
name: StepName.TariffSelection,
pathname: tariffSelectionPathname,
stepConfig: {
title: 'Tariff Selection',
},
},
{
name: StepName.DetailsPersonal,
pathname: personalDetailsPathname,
stepConfig: {
title: 'Details',
},
},
{
name: StepName.DetailsGeneral,
parentName: StepName.DetailsPersonal,
pathname: generalDetailsPathname,
},
{
name: StepName.DetailsPayment,
parentName: StepName.DetailsPersonal,
pathname: paymentDetailsPathname,
},
{
name: StepName.DetailsPaymentAlt,
parentName: StepName.DetailsPersonal,
pathname: paymentAltDetailsPathname,
},
{
name: StepName.Confirmation,
pathname: confirmationPathname,
stepConfig: {
title: 'Confirmation',
},
},
] as const satisfies WizardSteps<StepName>;

// ... pass this into WizardProvider (see below) ...
export const steps = (formValues: OnboardingFormValues) => {
if (formValues.tariffName === 'standard_fixed') {
return basicSteps;
}
return defaultSteps(formValues);
};

// initialise your wizard
export const {
useWizard: useOnboardingWizard,
WizardProvider: OnboardingWizardProvider,
WizardFormHydrator: OnboardingWizardFormHydrator,
} = createWizard({
storageKey: 'onboarding-sandbox',
defaultFormValues,
stepNames: StepName,
storage: storageConfig,
parseFormValues,
serializeFormValues,
});

// ... then pass your step function into your WizardProvider (possibly in another file with access to your router function) ...

export const OnboardingProvider = ({
children,
pathname,
push,
}: {
children: ReactNode | undefined;
pathname: string;
push: (url: UrlObject) => Promise<void | boolean> | void | boolean;
}) => {
return (
<OnboardingWizardProvider
pathname={pathname}
routingFn={push}
steps={steps}
>
{children}
</OnboardingWizardProvider>
);
};


Storing values in local storage to resume a journey

If you want to allow users to resume a journey from a link, one option is to store values in local storage. For that we need to pass allowArbitraryStart: true and storage to createWizard, and ensure we clear the storage using resetFormValues.

import { createWizard, StorageConfig } from '@krakentech/blueprint-onboarding';

...

export const storageConfig: StorageConfig = {
read: (storageKey) => {
const json = localStorage.getItem(storageKey);
if (json === null) {
return null;
}
return JSON.parse(json);
},
write: (storageKey, values) => {
localStorage.setItem(storageKey, JSON.stringify(values));
},
};

...

export const {
useWizard: useSimpleFormWizard,
WizardProvider: SimpleFormWizardProvider,
WizardFormHydrator: SimpleFormWizardFormHydrator,
} = createWizard({
storageKey: 'simple-form-sandbox',
defaultFormValues,
stepNames: StepName,
allowArbitraryStart: true,
storage: storageConfig,
});