npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gooonzick/wizard-solid

v1.12.0

Published

Solid.js integration for the WizardForm framework

Readme

@gooonzick/wizard-solid

Solid.js integration for the Wizard framework — a signal-backed createWizard() with fine-grained reactive getters.

Features

  • Signal-backed - every property is a reactive getter; read it in JSX or createEffect and only that channel is tracked
  • Atomic updates - state, navigation, validation and loading update in one batch(), so effects never see a half-applied transition
  • Two-way binding - wizard.field('name') returns { get value, set value } routed through machine.updateField
  • Context - <WizardProvider wizard={wizard}> + useWizardContext() / hasWizardContext()
  • Owner-aware lifecycle - destroyed with the owning component or createRoot; manual destroy() otherwise
  • Full Type Safety - TypeScript generics for your data types

Installation

npm install @gooonzick/wizard-solid @gooonzick/wizard-core
# or
pnpm add @gooonzick/wizard-solid @gooonzick/wizard-core

Requires solid-js 1.8 or newer (Solid 1.x). Solid 2.0 is not supported yet.

Quick Start

import { createLinearWizard } from "@gooonzick/wizard-core";
import { createWizard } from "@gooonzick/wizard-solid";
import { Match, Switch } from "solid-js";

type Signup = { name: string; plan: string };

const definition = createLinearWizard<Signup>({
	id: "signup",
	steps: [
		{
			id: "personal",
			title: "Personal",
			validate: async (d) =>
				d.name ? { valid: true } : { valid: false, errors: { name: "Required" } },
		},
		{ id: "plan", title: "Plan" },
	],
});

export function Signup() {
	const wizard = createWizard({
		definition,
		initialData: { name: "", plan: "basic" },
		onComplete: (data) => console.log("done", data),
	});
	const name = wizard.field("name");

	return (
		<form onSubmit={(e) => e.preventDefault()}>
			<h2>{wizard.currentStep.meta?.title}</h2>
			<Switch>
				<Match when={wizard.currentStepId === "personal"}>
					<input value={name.value} onInput={(e) => (name.value = e.currentTarget.value)} />
					<p>{wizard.validationErrors?.name}</p>
				</Match>
				<Match when={wizard.currentStepId === "plan"}>
					<p>Plan: {wizard.data.plan}</p>
				</Match>
			</Switch>
			<button
				type="button"
				onClick={() => void wizard.goPrevious().catch(() => {})}
				disabled={!wizard.canGoPrevious}
			>
				Back
			</button>
			<button
				type="button"
				onClick={() => void wizard.goNext().catch(() => {})}
				disabled={wizard.isNavigating}
			>
				{/* On the last step goNext() completes the wizard; canGoNext is false there. */}
				{wizard.progress.isLastStep ? "Finish" : "Next"}
			</button>
		</form>
	);
}

Reactivity rules

  • Read properties inside JSX, createEffect or createMemo. Destructuring (const { canGoNext } = wizard) reads once and loses reactivity — same as Solid props.
  • Tracking is per channel (state, navigation, validation, loading), not per field.
  • definition, initialData, context and plugins are read once. Recreate the wizard to reconfigure.

Things to know

  • canGoNext and isLastStep are seeded synchronously from progress.isLastStep (on creation and on every step change), so they are correct on first render for synchronous graphs; a step whose next is an async resolver starts as "not last" until the async computation settles. canGoPrevious and availableSteps come only from that async computation: right after creation canGoPrevious is false and availableSteps is empty.
  • canGoNext means "a next step exists", not "the current step is valid" — validation runs inside goNext().
  • Loading flags (isNavigating, isValidating, isSubmitting) are reference-counted: a flag stays true while any operation that set it is still in flight, so a double-clicked Next rejected as busy, or an overlapping validate(), no longer clears another operation's flag. isLoadingStep (a lazy step's implementation is loading) mirrors the machine and is not set by actions.preloadStep(stepId), which never rejects (safe to fire and forget; failures are reported by the navigation that needs the step).
  • goNext(), goPrevious(), goTo(), submit() and cancel() reject on failure (for example an invalid step) in addition to reporting to onError — await them in a try, or .catch(() => {}) when the UI already renders validationErrors. validate() resolves and exposes the result via isValid / validationErrors, even on an invalid step — it rejects only if the operation was already aborted (via an AbortSignal passed in context) before the call; a reset()/cancel() while it is in flight does not reject it, it just supersedes the result. validateAll() also resolves with a ValidationSummary even when steps are invalid (a throwing step validator counts as invalid) — it rejects if the wizard was already aborted, or if a step's enabled guard throws. A reset()/cancel()/restore()/destroy() fired while it's running does not cancel it, but with updateStatuses: true its step statuses are no longer written into the (now post-reset) state.

Lifecycle

Created inside a component or createRoot, the wizard is destroyed with its owner (autoDestroy: true by default). Created elsewhere (module scope, a shared store), call await wizard.destroy() yourself.

Errors

Machine errors go to onError. A createEffect that throws while the wizard updates its signals is also reported to onError (unless an <ErrorBoundary> catches it first): the wizard keeps working, but Solid may leave other effects of that update stale — wrap user effects in <ErrorBoundary> or catchError. This covers every signal update, including loading-flag changes and the async navigation recompute. Without an onError, these effect errors and reset() / restore() failures are logged with console.error instead of being dropped.

Documentation

License

MIT