useFormStepsInfo

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

View as Markdown

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

Parameter Type Description
steps Steps Steps configuration map (Record<string, Step>)
currentStepId string Active step ID
stepHistory string[] Stack of visited step IDs (excludes the current step)
fields Fields Field 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

Property Type Description
steps StepInfo[] Metadata for every step (see below)
currentStepId string Active step ID
totalSteps number Total number of steps
completedSteps number Count of completed steps
progress number Completion percentage (0-100)
stepHistory string[] Stack of visited step IDs
canGoNext boolean Heuristic: 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)
canGoPrev boolean Whether back navigation is possible (stepHistory is non-empty)
getStepInfo(id) (id: string) => StepInfo | undefined Get info for a specific step (undefined for unknown IDs)
getStepStatus(id) (id: string) => StepStatus Get status for a specific step ('pending' for unknown IDs)

StepInfo

Property Type Description
id string Step ID
fields string[] Field names in this step
fieldCount number Number of fields
status StepStatus 'pending', 'current', 'completed', or 'error'
hasErrors boolean Whether any fields have validation errors
errorCount number Number of fields with errors
isVisited boolean Whether the step has been visited
isCurrent boolean Whether this is the active step
isCompleted boolean Visited, not current, no errors, all fields valid
canNavigate boolean Whether 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