useFormStepsInfo

Step metadata for building progress bars, tab navigators, and step indicators.

useFormStepsInfo

Computes rich metadata for every step — status, error counts, completion, progress percentage. Designed to power UI components like progress bars, tab navigators, and step indicators.

If you don’t want to build your own UI, the package ships three prebuilt components on top of this hook: StepsProgress, StepsNavigation, and StepsAccordion — all take the same steps, currentStepId, stepHistory, and fields props. (StepsAccordion additionally requires Accordion components in your component registry — see Components.)


Signature

import { useFormStepsInfo, type FormStepsInfo, type StepInfo, type StepStatus } from '@saastro/forms';

const info = useFormStepsInfo({ steps, currentStepId, stepHistory, fields });

Must be called inside a react-hook-form FormProvider (it reads error state via useFormContext()). Anywhere inside <Form>’s children this is automatic. If you drive useFormState yourself, wrap your tree in <FormProvider {...methods}> (from react-hook-form) first.


Parameters

ParameterTypeDescription
stepsStepsSteps configuration map (Record<string, Step>)
currentStepIdstringActive step ID
stepHistorystring[]Stack of visited step IDs (excludes the current step)
fieldsFieldsField definitions map (Record<string, FieldConfig>)

These values typically come from useFormState: currentStepId and stepHistory are returned directly, and steps/fields are read from the resolved config it returns (config.steps, config.fields).


Return Value: FormStepsInfo

PropertyTypeDescription
stepsStepInfo[]Metadata for every step (see below)
currentStepIdstringActive step ID
totalStepsnumberTotal number of steps
completedStepsnumberCount of completed steps
progressnumberCompletion percentage (0-100)
stepHistorystring[]Stack of visited step IDs
canGoNextbooleanHeuristic: true when there is step history or more than one step (it does not check whether a next step actually exists from the current one)
canGoPrevbooleanWhether back navigation is possible (stepHistory is non-empty)
getStepInfo(id)(id: string) => StepInfo | undefinedGet info for a specific step (undefined for unknown IDs)
getStepStatus(id)(id: string) => StepStatusGet status for a specific step ('pending' for unknown IDs)

StepInfo

PropertyTypeDescription
idstringStep ID
fieldsstring[]Field names in this step
fieldCountnumberNumber of fields
statusStepStatus'pending', 'current', 'completed', or 'error'
hasErrorsbooleanWhether any fields have validation errors
errorCountnumberNumber of fields with errors
isVisitedbooleanWhether the step has been visited
isCurrentbooleanWhether this is the active step
isCompletedbooleanVisited, not current, no errors, all fields valid
canNavigatebooleanWhether the user can navigate to this step

StepStatus

type StepStatus = 'pending' | 'current' | 'completed' | 'error';
  • 'pending' — Not yet visited
  • 'current' — Active step with no validation errors (an active step with errors reports 'error')
  • 'completed' — Visited, not current, no errors, all fields valid
  • 'error' — Has validation errors (current or visited)

Reactivity note: the result is memoized and reads field values non-reactively (via getValues()). It refreshes when the current step, the step history, or validation errors change — not on every keystroke.


Example: Custom Progress Bar

import { useFormStepsInfo, type Fields, type Steps } from '@saastro/forms';

interface StepProgressProps {
  steps: Steps;
  currentStepId: string;
  stepHistory: string[];
  fields: Fields;
}

function StepProgress({ steps, currentStepId, stepHistory, fields }: StepProgressProps) {
  const info = useFormStepsInfo({ steps, currentStepId, stepHistory, fields });

  return (
    <div>
      <div className="flex gap-2 mb-4">
        {info.steps.map((step) => (
          <div
            key={step.id}
            className={`flex-1 h-2 rounded ${
              step.isCompleted
                ? 'bg-green-500'
                : step.isCurrent
                  ? 'bg-blue-500'
                  : step.hasErrors
                    ? 'bg-red-500'
                    : 'bg-gray-200'
            }`}
          />
        ))}
      </div>
      <p className="text-sm text-muted-foreground">
        Step {info.steps.findIndex((s) => s.isCurrent) + 1} of {info.totalSteps} ({info.progress}%
        complete)
      </p>
    </div>
  );
}

Example: Step Navigator with Error Badges

import { useFormStepsInfo, type Fields, type Steps } from '@saastro/forms';

interface StepNavProps {
  steps: Steps;
  currentStepId: string;
  stepHistory: string[];
  fields: Fields;
  onStepClick: (stepId: string) => void;
}

function StepNav({ steps, currentStepId, stepHistory, fields, onStepClick }: StepNavProps) {
  const info = useFormStepsInfo({ steps, currentStepId, stepHistory, fields });

  return (
    <nav className="flex gap-1">
      {info.steps.map((step, i) => (
        <button
          key={step.id}
          onClick={() => step.canNavigate && onStepClick(step.id)}
          disabled={!step.canNavigate}
          className={`px-3 py-1 rounded text-sm ${
            step.isCurrent
              ? 'bg-primary text-white'
              : step.isCompleted
                ? 'bg-green-100 text-green-800'
                : 'bg-gray-100 text-gray-500'
          }`}
        >
          {i + 1}. {step.id}
          {step.hasErrors && <span className="ml-1 text-xs text-red-500">({step.errorCount})</span>}
        </button>
      ))}
    </nav>
  );
}

  • Multi-Step Forms — Full guide on step configuration, branching, and navigation
  • useFormState — The form engine that provides currentStepId, stepHistory, and the resolved config