Skip to content

Demos#

Basic usage#

Heading

Contents

Contents

const initialData = {
  firstName: 'John',
  lastName: 'Doe',
  streetName: 'Osloveien',
  streetNr: 12,
  postalCode: '1234',
  city: 'Oslo',
}
const Step1 = () => (
  <Wizard.Step aria-label="Basic wizard: Step 1" title="Step 1">
    <Form.MainHeading>Heading</Form.MainHeading>
    <Form.Card>
      <P>Contents</P>
    </Form.Card>
    <Form.Card>
      <P>Contents</P>
    </Form.Card>

    <Wizard.Buttons />
  </Wizard.Step>
)
const Step2 = () => (
  <Wizard.Step aria-label="Basic wizard: Step 2" title="Step 2">
    <Form.MainHeading>Heading</Form.MainHeading>
    <Form.Card>
      <P>Contents</P>
    </Form.Card>
    <Form.Card>
      <P>Contents</P>
    </Form.Card>

    <Wizard.Buttons />
  </Wizard.Step>
)
const Summary = () => {
  const { summaryTitle } = Form.useTranslation().Step
  return (
    <Wizard.Step
      aria-label={`Basic wizard: ${summaryTitle}`}
      title={summaryTitle}
    >
      <Form.MainHeading>Summary</Form.MainHeading>
      <Form.Card>
        <Form.SubHeading>Deliver address</Form.SubHeading>

        <Value.SummaryList layout="grid">
          <Value.Name.First path="/firstName" />
          <Value.Name.Last path="/lastName" />

          <Value.Composition label="Street">
            <Value.String path="/streetName" />
            <Value.Number path="/streetNr" />
          </Value.Composition>

          <Value.Composition label="City">
            <Value.String path="/postalCode" />
            <Value.String path="/city" />
          </Value.Composition>
        </Value.SummaryList>

        <Wizard.EditButton toStep={1} />
      </Form.Card>

      <Form.ButtonRow>
        <Wizard.Buttons />
        <Form.SubmitButton variant="send" />
      </Form.ButtonRow>
    </Wizard.Step>
  )
}

// Can be an async function, in case you need to make some async stuff
const onStepChange = async (step, mode) => {
  if (mode === 'next') {
    await new Promise((resolve) => setTimeout(resolve, 1000))
  }
  console.log('onStepChange', step, mode)
}

// Can be an async function, in case you need to make some async stuff
const onSubmit = async (data) => {
  await new Promise((resolve) => setTimeout(resolve, 2000))
  console.log('onSubmit', data)
}
const MyForm = () => {
  // Routers like "react-router" are supported as well
  Wizard.useQueryLocator('my-wizard')
  return (
    <Form.Handler data={initialData} onSubmit={onSubmit}>
      <Provider
        StepIndicator={{
          overviewTitle: 'Basic wizard steps',
        }}
      >
        <Wizard.Container id="my-wizard" onStepChange={onStepChange}>
          <Step1 />
          <Step2 />
          <Summary />
        </Wizard.Container>
      </Provider>
    </Form.Handler>
  )
}
render(<MyForm />)

Async wizard#

const MyForm = () => {
  const onStepChange = useCallback(async (index, mode) => {
    console.log('onStepChange', index)
    if (mode === 'next') {
      try {
        const request = createRequest()
        await request(1000) // Simulate a request
      } catch (error) {
        return error instanceof Error ? error : new Error(String(error))
      }
    }

    // Optional, you can show a FormStatus at the bottom of the form
    return {
      info: `Info message: ${index}`,
    }
  }, [])
  const onSubmit = useCallback(async (data) => {
    console.log('onSubmit', data)
    try {
      const request = createRequest()
      await request(1000) // Simulate a request
    } catch (error) {
      return error instanceof Error ? error : new Error(String(error))
    }

    // Optional, you can show a FormStatus at the bottom of the form
    return {
      warning: 'Warning message',
    }
  }, [])
  const validator = useCallback(async (value) => {
    try {
      const request = createRequest()
      await request(1000) // Simulate a request
    } catch (error) {
      return error instanceof Error ? error : new Error(String(error))
    }
    if (value === 'invalid') {
      return Error('Error message')
    }
  }, [])
  const validator1 = debounceAsync(validator)
  const validator2 = debounceAsync(validator)
  const Step1 = () => {
    return (
      <Wizard.Step aria-label="Asynchronous wizard: Step 1" title="Step 1">
        <Form.Card>
          <Field.String
            label="Required field with async validator"
            onChangeValidator={validator1}
            path="/field1"
            required
          />
          <Field.String
            label="Field with async validator"
            onChangeValidator={validator2}
            path="/field2"
          />
        </Form.Card>

        <Wizard.Buttons />
      </Wizard.Step>
    )
  }
  const Step2 = () => {
    return (
      <Wizard.Step aria-label="Asynchronous wizard: Step 2" title="Step 2">
        <Form.MainHeading>Heading</Form.MainHeading>

        <Form.Card>
          <P>Contents of step 2</P>
        </Form.Card>

        <Form.ButtonRow>
          <Wizard.Buttons />
          <Form.SubmitButton variant="send" />
        </Form.ButtonRow>
      </Wizard.Step>
    )
  }
  return (
    <Form.Handler onSubmit={onSubmit}>
      <Provider
        StepIndicator={{
          overviewTitle: 'Asynchronous wizard steps',
        }}
      >
        <Wizard.Container onStepChange={onStepChange}>
          <Step1 />
          <Step2 />
        </Wizard.Container>
      </Provider>
    </Form.Handler>
  )
}
render(<MyForm />)

With StatusMessage in Menu#

This example uses the loose mode to demonstrate status messages. Press the Send button to see the status message. You may also navigate to the previous steps and press the Send button again.

<Form.Handler
  onSubmit={(data) => {
    console.log('onSubmit', data)
  }}
>
  <Provider
    StepIndicator={{
      overviewTitle: 'Wizard menu status steps',
    }}
  >
    <Wizard.Container
      onStepChange={async (index, mode) => {
        console.log('onStepChange', index, mode)
      }}
      mode="loose"
      initialActiveIndex={2}
    >
      <Wizard.Step aria-label="Wizard menu status: Step 1" title="Step 1">
        <Field.String label="Step 1" path="/step1" required />
        <Wizard.Buttons />
      </Wizard.Step>

      <Wizard.Step aria-label="Wizard menu status: Step 2" title="Step 2">
        <Field.String label="Step 2" path="/step2" required />
        <Wizard.Buttons />
      </Wizard.Step>

      <Wizard.Step aria-label="Wizard menu status: Step 3" title="Step 3">
        <Field.String label="Step 3" path="/step3" />
        <Wizard.Buttons />
      </Wizard.Step>
    </Wizard.Container>
  </Provider>

  <Form.SubmitButton />
</Form.Handler>

With StatusMessage#

Step 1

Content

<Form.Handler>
  <Provider
    StepIndicator={{
      overviewTitle: 'Wizard status message steps',
    }}
  >
    <Wizard.Container
      onStepChange={async (index, mode, { preventNavigation }) => {
        preventNavigation()
        return {
          info: 'Info message.',
          warning: 'Warning message.',
        }
      }}
    >
      <Wizard.Step
        aria-label="Wizard status message: Step 1"
        title="Step 1"
      >
        <Form.MainHeading>Step 1</Form.MainHeading>
        <P>Content</P>
        <Wizard.NextButton text="Press me to see the status message" />
      </Wizard.Step>
    </Wizard.Container>
  </Provider>
</Form.Handler>

Get errors before submit or step change#

You can use the onSubmitRequest property on the Form.Handler to get visible errors before the form is submitted.

Each item in the error array contains the following properties in an object:

  • path The path of the field.
  • value The value of the field.
  • displayValue The displayed value of the field.
  • label The label of the field.
  • props The given field properties.
  • error The error of the field.
const onSubmitRequest: OnSubmitRequest = ({ getErrors }) => {
getErrors().forEach(
({ path, value, displayValue, label, props, error }) => {
// Do something with the error
console.log(label, error.message)
}
)
}
<Form.Handler
  onSubmitRequest={({ getErrors }) => {
    getErrors().forEach(({ label, error }) => {
      console.log(label, error.message)
    })
  }}
>
  <Provider
    StepIndicator={{
      overviewTitle: 'Wizard submit request steps',
    }}
  >
    <Wizard.Container mode="loose">
      <Wizard.Step
        aria-label="Wizard submit request: Step 1"
        title="Step 1"
      >
        <Form.Card>
          <Field.String
            path="/foo"
            label="Foo"
            defaultValue="With default value"
            required
          />
          <Field.String path="/bar" label="Bar" required />
        </Form.Card>

        <Wizard.Buttons />
      </Wizard.Step>

      <Wizard.Step
        aria-label="Wizard submit request: Step 2"
        title="Step 2"
      >
        <Form.Card>
          <Field.String path="/baz" label="Baz" required />
        </Form.Card>

        <Wizard.Buttons />

        <Form.SubmitButton />
      </Wizard.Step>
    </Wizard.Container>
  </Provider>
</Form.Handler>
Suggest an edit