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.
- OrganizationBusiness details
- 2AccountYour credentials
- 3ReviewConfirm details
Usage
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
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).
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