Devtools
@domphy/form broadcasts form state to the TanStack Form browser devtools automatically. Every form mounts an event listener and emits its state on every change — no extra configuration required.
Install the browser extension
Install the TanStack Form Devtools browser extension from the Chrome Web Store (also available for Firefox via the add-ons site).
Once installed, a TanStack Form panel appears in the browser DevTools. Open it to see all mounted forms on the current page.
What the devtools show
For each form:
- All current values
- Form-level errors and validation state
- Per-field metadata:
isTouched,isDirty,isValidating,errors,errorMap - Submission flags:
isSubmitting,isSubmitted,submissionAttempts canSubmitstatus
Actions available from the panel:
- Reset — calls
form.reset()directly - Force submit — triggers
form.handleSubmit()bypassing validation guards
Naming forms with formId
When multiple forms are on the same page, give each one a formId to identify it in the devtools panel:
import { createForm } from "@domphy/form/domphy"
const loginForm = createForm<{ email: string; password: string }>({
formId: "login-form", // shows as "login-form" in devtools
defaultValues: { email: "", password: "" },
onSubmit: ({ value }) => login(value),
})
const signupForm = createForm<{ email: string; name: string }>({
formId: "signup-form",
defaultValues: { email: "", name: "" },
onSubmit: ({ value }) => signup(value),
})Without formId, each form gets a random UUID that changes on page reload.
How the integration works
createForm() internally calls form.mount(), which wires up an EventClient (from @tanstack/devtools-event-client) that:
- Emits
form-apion mount and on every store change — sends current state + options to the devtools panel. - Listens for
request-form-state— responds with the current state when the panel requests it. - Listens for
request-form-reset— callsform.reset()when the Reset button is clicked in the panel. - Listens for
request-form-force-submit— callsform.handleSubmit()when Force Submit is clicked. - Emits
form-unmountedwhenform.destroy()is called — removes the form from the panel.
This happens automatically. You do not need to import or configure the EventClient.
Debugging form state without devtools
You can inspect form state programmatically at any time:
// Non-reactive snapshot — reads current state without subscribing
const snapshot = form.form.state
console.log(snapshot.values) // current field values
console.log(snapshot.isValid) // overall validity
console.log(snapshot.fieldMeta) // per-field metadata map
console.log(snapshot.errors) // form-level errors
// Per-field snapshot
const emailMeta = form.form.getFieldMeta("email")
console.log(emailMeta?.errors) // errors for the email field
console.log(emailMeta?.isTouched) // whether the user has blurred the fieldTracking form changes with version(l)
The form handle exposes version(l) — a reactive change counter that increments on every store flush. Use it to log every change during development:
import { effect } from "@domphy/core"
const form = createForm<{ name: string }>({
defaultValues: { name: "" },
onSubmit: ({ value }) => save(value),
})
// Log the full state on every change
effect(() => {
form.version() // subscribe (no listener in plain effect)
const state = form.form.state
console.log("[form]", {
values: state.values,
isValid: state.isValid,
isDirty: state.isDirty,
})
})Remove the effect call before shipping to production.
Subscribing to the raw store
For custom diagnostics, subscribe directly to the underlying TanStack store:
const unsubscribe = form.form.store.subscribe(() => {
const { values, isValid, isSubmitting } = form.form.state
myAnalytics.track("form_state_change", { isValid, isSubmitting })
})
// Later: clean up
unsubscribe.unsubscribe()Cleanup on unmount
Always call form.destroy() when the form element is removed from the DOM to unregister the devtools listener and clean up field subscriptions:
const FormElement = {
form: [ /* ... */ ],
_onRemove: () => form.destroy(),
}If destroy() is not called, the devtools will still show the form as mounted and the form-unmounted event will never fire.