npm

Reference

Steps

A controlled, themed step indicator for multi-step forms and wizards. Pass the steps and the current index, and the rest follows: completed steps fill with the primary color and a check, the current step is ringed, and upcoming steps stay muted.

Try it

Click through the states. Each one is drawn entirely from theme tokens, in both light and dark.

  1. 1OrganizationBusiness details
  2. 2AccountYour credentials
  3. 3ReviewConfirm details

Usage

steps.tsx
import { Steps, type Step } from "@viliha/vui-ui/steps";

const steps: Step[] = [
  { label: "Organization", description: "Business details" },
  { label: "Account", description: "Your credentials" },
  { label: "Review", description: "Confirm details" },
];

const [current, setCurrent] = useState(0);

<Steps steps={steps} current={current} />

Props

Steps
type Step = {
  label: string;        // shown under the marker
  description?: string; // optional secondary line
};

function Steps(props: {
  steps: Step[];
  current: number;      // zero-based index of the active step
  className?: string;
});

Wizard scaffold

Steps is only the indicator. For a full multi-step form use the wizard scaffold (@viliha/vui-ui/wizard), it's layout only, so you keep your own step index, field state, and logic and drop any components inside. Wizard gives you the stepper, a scrolling body, and a Back/Next footer; WizardSection is a bordered title/icon card: put one or many per step. Fields go in a FieldGrid as the two-column Label * │ control standard (from @viliha/vui-ui/field-grid).

wizard.tsx
import { Wizard, WizardSection } from "@viliha/vui-ui/wizard";
import { FieldGrid, Field } from "@viliha/vui-ui/field-grid";
import { Input } from "@viliha/vui-ui/input";

const STEPS = [
  { label: "Organization", description: "Business details" },
  { label: "Account", description: "Your credentials" },
  { label: "Review", description: "Confirm details" },
];

function Onboarding() {
  const [step, setStep] = useState(0);
  const last = step === STEPS.length - 1;

  return (
    <Wizard
      steps={STEPS}
      current={step}
      onBack={() => setStep((s) => s - 1)}
      onNext={() => (last ? submit() : setStep((s) => s + 1))}
      backDisabled={step === 0}
      nextLabel={last ? "Create account" : undefined}
    >
      {step === 0 && (
        <WizardSection title="Basic Information" icon={Building}>
          <FieldGrid>
            <Field label="Name" htmlFor="name" required>
              <Input id="name" value={name} onChange={onName} />
            </Field>
          </FieldGrid>
        </WizardSection>
      )}
      {/* …more steps / sections… */}
    </Wizard>
  );
}

Layout, not a form engine

The scaffold never touches your data: no values, validation, or submission. That stays yours; the wizard just guarantees the structure and the two-column field design. A live example is the /steps page in the demo, and the Multi-step wizard pattern.