Get Started
Using the useStepper hook

Using Headless Stepper

Create a component that will use Headless Stepper is too easy. Basically, you need to import the hook and create an instance to all objects that we need, or you can destructure the props that the useStepper() hook returns.

You, as developer, are responsible for rendering the UI. This guide will show you how to use the hook and how to render the UI (without styles).

Starting from the basic

When thinking about a stepper component, you imagine something with labels, and buttons to trigger an action and go to the next step, so,let's see how to use the hook in a simple case.

First of all, we need to import the hook.

import { useStepper } from 'headless-stepper';

Now, let's create the steps:

const steps = React.useMemo(
  () => [
    {
      label: 'Step 1',
    },
    { label: 'Step 2' },
    { label: 'Step 3' },
    { label: 'Step 4', disabled: true },
    { label: 'Step 5' },
    { label: 'Step 6' },
  ],
  []
);
⚠️

We're using React.useMemo to avoid useStepper doesn't recalculate on every single render. Only when the memoized value actually changes!

Using the useStepper() hook

Now that you have the steps, you can use the useStepper() hook to create the stepper.

const stepperInstance = useStepper({ steps });

Or, if you want to destructure the props, you can do it like this:

const { state, nextStep, prevStep, progressProps, stepsProps, stepperProps } =
  useStepper({ steps });

As you can see, the hook receives an object with the steps list. These steps list might be a simple array of objects. Now, let's build the UI.

Building the UI

return (
  <div>
    <div>
      <nav style={{ display: 'flex' }} {...stepperProps}>
        {stepsProps?.map((step, index) => (
          <ol
            key={index}
            style={{
              opacity: steps[index].disabled ? 0.6 : 1,
              fontWeight: state.currentStep === index ? 'bold' : 'unset',
            }}
          >
            <a {...step}>{steps[index].label}</a>
          </ol>
        ))}
      </nav>
    </div>
    <p>Current step: {state.currentStep}</p>
    <button onClick={prevStep} disabled={!state.hasPreviousStep}>
      Prev
    </button>
    <button onClick={nextStep}>Next</button>
    <div {...progressProps} />
  </div>
);

And, this is the final result:


  import React from 'react';
  import { useStepper } from 'headless-stepper';
  interface HeadlessStepperProps {}

  export default function HeadlessStepper(props: HeadlessStepperProps) {
    const steps = React.useMemo(
      () => [
        {
          label: 'Step 1',
        },
        { label: 'Step 2' },
        { label: 'Step 3' },
        { label: 'Step 4', disabled: true },
        { label: 'Step 5' },
        { label: 'Step 6' },
      ],
      []
    );
    const { state, nextStep, prevStep, progressProps, stepsProps, stepperProps } =
      useStepper({
        steps,
      });
    return (
      <div>
        <div>
          <nav style={{ display: 'flex' }} {...stepperProps}>
            {stepsProps?.map((step, index) => (
              <ol
                key={index}
                style={{
                  opacity: steps[index].disabled ? 0.6 : 1,
                  fontWeight: state.currentStep === index ? 'bold' : 'unset',
                }}
              >
                <a {...step}>{steps[index].label}</a>
              </ol>
            ))}
          </nav>
        </div>
        <p>Current step: {state.currentStep}</p>
        <button onClick={prevStep} disabled={!state.hasPreviousStep}>
          Prev
        </button>
        <button onClick={nextStep} disabled={!state.hasNextStep}>Next</button>
        <div {...progressProps} />
        <p>React Version: {React.version}</p>
        <p>State:</p>
        <pre style={{backgroundColor: '#f2f2f2'}}>
          {JSON.stringify(state, null, 2)}
        </pre>
      </div>
    );
  }

Accessibility notes

The stepper props that useStepper() hook returns is treated as a tab component with the role="tablist" for the steps wrapper, and role="tab" for each step.

Inspect the above example and you'll see that the aria-selected attribute is set to true for the current step.

Keyboard interaction

Keyboard keyDescription
TabFocus on first step or focus on the current step
ArrowLeftGo to previous step.
ArrowRightGo to next step.
EnterSelect the focused step.
SpaceSelect the focused step.