Back to all examples

Form stepper with controlled navigation

const [step, setStep] = useState(1);

  function setPage(page: number) {
    if (page < 1 || page > 4) return;
    setStep(page);
  }
<GoabFormStepper step={step} onChange={(event: GoabFormStepperOnChangeDetail) => setStep(event.step)}>
        <GoabFormStep text="Personal details" />
        <GoabFormStep text="Employment history" />
        <GoabFormStep text="References" />
        <GoabFormStep text="Review" />
      </GoabFormStepper>

      <GoabPages current={step} mb="3xl" mt="xl" mr="xl" ml="xl">
        <div>
          <GoabSkeleton type="article" />
        </div>
        <div>
          <GoabSkeleton type="header" size="2" />
          <GoabSkeleton type="text" />
          <GoabSkeleton type="header" size="2" />
          <GoabSkeleton type="text" />
        </div>
        <div>
          <GoabSkeleton type="text" />
          <GoabSpacer vSpacing="m" />
          <GoabSkeleton type="text" />
        </div>
        <div>
          <GoabSkeleton type="header" size="2" />
          <GoabSkeleton type="text" />
          <GoabSpacer vSpacing="m" />
          <GoabSkeleton type="text" />
        </div>
      </GoabPages>

      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <GoabxButton type="secondary" onClick={() => setPage(step - 1)}>
          Previous
        </GoabxButton>
        <GoabxButton type="primary" onClick={() => setPage(step + 1)}>
          Next
        </GoabxButton>
      </div>
step = 1;

  updateStep(event: GoabFormStepperOnChangeDetail): void {
    this.step = event.step;
  }

  setPage(page: number): void {
    if (page < 1 || page > 4) return;
    this.step = page;
  }
<goab-form-stepper ml="s" mr="s" [step]="step" (onChange)="updateStep($event)">
  <goab-form-step text="Personal details"></goab-form-step>
  <goab-form-step text="Employment history"></goab-form-step>
  <goab-form-step text="References"></goab-form-step>
  <goab-form-step text="Review"></goab-form-step>
</goab-form-stepper>

<goab-pages [current]="step" mb="3xl" mt="xl" mr="xl" ml="xl">
  <div>
    <goab-skeleton type="article"></goab-skeleton>
  </div>
  <div>
    <goab-skeleton type="header" size="2"></goab-skeleton>
    <goab-skeleton type="text"></goab-skeleton>
    <goab-skeleton type="header" size="2"></goab-skeleton>
    <goab-skeleton type="text"></goab-skeleton>
  </div>
  <div>
    <goab-skeleton type="text"></goab-skeleton>
    <goab-spacer vSpacing="m"></goab-spacer>
    <goab-skeleton type="text"></goab-skeleton>
  </div>
  <div>
    <goab-skeleton type="header" size="2"></goab-skeleton>
    <goab-skeleton type="text"></goab-skeleton>
    <goab-spacer vSpacing="m"></goab-spacer>
    <goab-skeleton type="text"></goab-skeleton>
  </div>
</goab-pages>

<div style="display: flex; justify-content: space-between">
  <goabx-button (onClick)="setPage(step - 1)" type="secondary">Previous</goabx-button>
  <goabx-button (onClick)="setPage(step + 1)" type="primary">Next</goabx-button>
</div>
const formStepper = document.getElementById('form-stepper');
const formPages = document.getElementById('form-pages');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');

let currentStep = 1;

function updateStep(step) {
  if (step < 1 || step > 4) return;
  currentStep = step;
  formStepper.setAttribute('step', currentStep);
  formPages.setAttribute('current', currentStep);
}

formStepper.addEventListener('_change', (e) => {
  updateStep(e.detail.step);
});

prevBtn.addEventListener('_click', () => {
  updateStep(currentStep - 1);
});

nextBtn.addEventListener('_click', () => {
  updateStep(currentStep + 1);
});
<goa-form-stepper id="form-stepper" ml="s" mr="s" step="1">
  <goa-form-step text="Personal details"></goa-form-step>
  <goa-form-step text="Employment history"></goa-form-step>
  <goa-form-step text="References"></goa-form-step>
  <goa-form-step text="Review"></goa-form-step>
</goa-form-stepper>

<goa-pages id="form-pages" current="1" mb="3xl" mt="xl" mr="xl" ml="xl">
  <div>
    <goa-skeleton type="article"></goa-skeleton>
  </div>
  <div>
    <goa-skeleton type="header" size="2"></goa-skeleton>
    <goa-skeleton type="text"></goa-skeleton>
    <goa-skeleton type="header" size="2"></goa-skeleton>
    <goa-skeleton type="text"></goa-skeleton>
  </div>
  <div>
    <goa-skeleton type="text"></goa-skeleton>
    <goa-spacer vspacing="m"></goa-spacer>
    <goa-skeleton type="text"></goa-skeleton>
  </div>
  <div>
    <goa-skeleton type="header" size="2"></goa-skeleton>
    <goa-skeleton type="text"></goa-skeleton>
    <goa-spacer vspacing="m"></goa-spacer>
    <goa-skeleton type="text"></goa-skeleton>
  </div>
</goa-pages>

<div style="display: flex; justify-content: space-between">
  <goa-button version="2" id="prev-btn" type="secondary">Previous</goa-button>
  <goa-button version="2" id="next-btn" type="primary">Next</goa-button>
</div>

Create a multi-step form with controlled navigation using Previous/Next buttons.

When to use

Use this pattern when:

  • A form is too long to display on a single page
  • You want to guide users through a sequential process
  • Steps must be completed in order before proceeding
  • Users should not be able to skip ahead to incomplete steps

Considerations

  • Set an initial step value >= 1 to enable controlled mode
  • Steps that are “Not started” will not be clickable
  • Use Previous/Next buttons to control navigation programmatically
  • Validate step completion before allowing navigation forward
  • Consider showing skeleton content while loading step data