Getting Started
Blocks
About
The Form Management TanStack system provides a comprehensive solution for handling forms with auto-save functionality, unsaved changes warnings, and a sticky action bar that appears when there are pending changes. Built on top of TanStack Form for powerful type-safe form state management.
Features
- ✅ Auto-save: Automatically save form changes with configurable debounce
- ✅ Sticky Action Bar: Beautiful floating action bar for unsaved changes
- ✅ Unsaved Changes Warning: Warns users before leaving with unsaved changes
- ✅ Keyboard Shortcuts: CMD/Ctrl+S to save changes
- ✅ Loading States: Built-in loading indicators during submission
- ✅ TanStack Form: Full integration with @tanstack/react-form
- ✅ TypeScript: Fully typed for better developer experience
- ✅ Zod Validation: Schema-based validation with Zod
Preview
Form Management with TanStack Form
Edit and see the sticky save bar appear (CMD+S to save)
"use client"
import { toast } from "sonner"
import { z } from "zod"
import { Card } from "@/components/ui/card"
import { FormManagement } from "@/registry/nowts/blocks/form-management-tanstack/form-management"
import { FormManagementStickyBar } from "@/registry/nowts/blocks/form-management-tanstack/form-management-sticky-bar"
import { useForm } from "@/components/ui/tanstack-form"
const profileSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email address"),
bio: z.string().max(200, "Bio must be less than 200 characters"),
})
export function FormManagementTanstackDemo() {
const form = useForm({
schema: profileSchema,
defaultValues: {
name: "John Doe",
email: "john@example.com",
bio: "Full-stack developer passionate about building great products",
},
onSubmit: async (values) => {
await new Promise((resolve) => setTimeout(resolve, 1500))
toast.success("Profile saved successfully!")
form.reset()
},
})
return (
<Card className="p-6">
<div className="mb-6">
<h3 className="text-lg font-semibold">
Form Management with TanStack Form
</h3>
<p className="text-muted-foreground text-sm">
Edit and see the sticky save bar appear (CMD+S to save)
</p>
</div>
<FormManagement form={form}>
<div className="space-y-4">
<form.AppField name="name">
{(field) => (
<field.Field>
<field.Label>Name</field.Label>
<field.Content>
<field.Input placeholder="Enter your name" />
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
<form.AppField name="email">
{(field) => (
<field.Field>
<field.Label>Email</field.Label>
<field.Content>
<field.Input type="email" placeholder="Enter your email" />
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
<form.AppField name="bio">
{(field) => (
<field.Field>
<field.Label>Bio</field.Label>
<field.Content>
<field.Textarea
placeholder="Tell us about yourself"
rows={3}
/>
<field.Description>
Maximum 200 characters ({field.state.value?.length || 0}
/200)
</field.Description>
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
</div>
<FormManagementStickyBar />
</FormManagement>
</Card>
)
}
Installation
1. Install the component
pnpm dlx shadcn@latest add https://ui.nowts.app/r/form-management-tanstack.json
2. Install additional dependencies
The form management system requires some additional components:
pnpm dlx shadcn@latest add https://ui.nowts.app/r/use-debounce-fn.json https://ui.nowts.app/r/use-warn-if-unsaved-changes.json https://ui.nowts.app/r/use-is-client.json https://ui.nowts.app/r/submit-button.json https://ui.nowts.app/r/tanstack-form.json
Usage
Basic form with sticky bar
import { z } from "zod"
import { FormManagement } from "@/lib/form-management-tanstack/form-management"
import { FormManagementStickyBar } from "@/lib/form-management-tanstack/form-management-sticky-bar"
import { useForm } from "@/components/ui/tanstack-form"
const schema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email"),
})
export function BasicForm() {
const form = useForm({
schema,
defaultValues: { name: "", email: "" },
onSubmit: async (values) => {
await saveUserData(values)
},
})
return (
<FormManagement form={form}>
<div className="space-y-4">
<form.AppField name="name">
{(field) => (
<field.Field>
<field.Label>Name</field.Label>
<field.Content>
<field.Input placeholder="Your name" />
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
<form.AppField name="email">
{(field) => (
<field.Field>
<field.Label>Email</field.Label>
<field.Content>
<field.Input type="email" placeholder="your@email.com" />
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
</div>
<FormManagementStickyBar />
</FormManagement>
)
}Form with auto-save
import {
FormManagement,
FormManagementAutoSave,
} from "@/lib/form-management-tanstack/form-management"
export function AutoSaveForm() {
const form = useForm({
schema: profileSchema,
defaultValues: { title: "", content: "" },
onSubmit: async (values) => {
await autosaveData(values)
},
})
return (
<FormManagement form={form}>
<div className="space-y-4">
<form.AppField name="title">
{(field) => (
<field.Field>
<field.Label>Title</field.Label>
<field.Content>
<field.Input placeholder="Document title" />
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
<form.AppField name="content">
{(field) => (
<field.Field>
<field.Label>Content</field.Label>
<field.Content>
<field.Textarea placeholder="Document content" />
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
</div>
{/* Auto-save every 3 seconds */}
<FormManagementAutoSave form={form} autoSaveMs={3000} />
</FormManagement>
)
}Custom sticky bar labels
<FormManagementStickyBar actionLabel="Publish Changes" cancelLabel="Discard" />Form with complex validation
import { z } from "zod"
const profileSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email address"),
bio: z.string().max(200, "Bio must be less than 200 characters"),
age: z.number().min(18, "Must be at least 18 years old"),
})
export function ValidatedForm() {
const form = useForm({
schema: profileSchema,
defaultValues: { name: "", email: "", bio: "", age: 18 },
onSubmit: async (values) => {
await createUser(values)
},
})
return (
<FormManagement form={form}>
<div className="space-y-4">
<form.AppField name="name">
{(field) => (
<field.Field>
<field.Label>Name</field.Label>
<field.Content>
<field.Input placeholder="Enter your name" />
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
<form.AppField name="email">
{(field) => (
<field.Field>
<field.Label>Email</field.Label>
<field.Content>
<field.Input type="email" placeholder="your@email.com" />
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
<form.AppField name="bio">
{(field) => (
<field.Field>
<field.Label>Bio</field.Label>
<field.Content>
<field.Textarea placeholder="Tell us about yourself" />
<field.Message />
</field.Content>
</field.Field>
)}
</form.AppField>
</div>
<FormManagementStickyBar />
</FormManagement>
)
}Manual form actions
You can access form actions using the context:
import { useFormManagement } from "@/lib/form-management-tanstack/form-management"
import { Button } from "@/components/ui/button"
function CustomActions() {
const { isDirty, isLoading, submit, cancel } = useFormManagement()
return (
<div className="flex gap-2">
<Button onClick={cancel} variant="outline" disabled={!isDirty}>
Reset
</Button>
<Button onClick={submit} disabled={!isDirty} loading={isLoading}>
Save Now
</Button>
</div>
)
}
// Use inside FormManagement
;<FormManagement form={form}>
{/* form fields */}
<CustomActions />
</FormManagement>API Reference
FormManagement
| Prop | Type | Description |
|---|---|---|
form | ReturnType<typeof useForm> | TanStack Form instance |
children | React.ReactNode | Form fields and components |
FormManagementStickyBar
| Prop | Type | Default | Description |
|---|---|---|---|
actionLabel | string | "Save" | Save button label |
cancelLabel | string | "Reset" | Cancel button label |
FormManagementAutoSave
| Prop | Type | Description |
|---|---|---|
form | ReturnType<typeof useForm> | TanStack Form instance |
autoSaveMs | number | Auto-save debounce in milliseconds |
useFormManagement
Returns form management context:
{
isDirty: boolean; // Form has unsaved changes
isLoading: boolean; // Form is submitting
submit: () => void; // Trigger form submission
cancel: () => void; // Reset form to initial state
}Differences from Form Management (React Hook Form)
The TanStack Form version provides several advantages:
- Better Type Safety: TanStack Form provides superior TypeScript support with automatic type inference
- Field Components: Uses field components from the TanStack Form integration for cleaner code
- Validation Modes: More flexible validation options with
onChange,onBlur, andonSubmit - Performance: TanStack Form is optimized for performance with fine-grained reactivity
- Array & Nested Fields: Better support for complex form structures with arrays and nested objects
Choose this version if you're already using TanStack Form in your project or prefer its API over React Hook Form.
On This Page
AboutFeaturesPreviewInstallation1. Install the component2. Install additional dependenciesUsageBasic form with sticky barForm with auto-saveCustom sticky bar labelsForm with complex validationManual form actionsAPI ReferenceFormManagementFormManagementStickyBarFormManagementAutoSaveuseFormManagementDifferences from Form Management (React Hook Form)Build Your SaaS in Days, Not Months
NOW.TS is the Next.js 15 boilerplate with everything you need to launch your SaaS—auth, payments, database, and AI-ready infrastructure.
Learn more about NOW.TS