Getting Started
Blocks
About
The Form Management 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.
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
- ✅ React Hook Form: Full integration with react-hook-form
- ✅ TypeScript: Fully typed for better developer experience
Preview
Form Management
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 { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { FormManagement } from "@/registry/nowts/blocks/form-management/form-management"
import { FormAutoSaveStickyBar } from "@/registry/nowts/blocks/form-management/form-management-sticky-bar"
import { useZodForm } from "@/components/ui/extended-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 FormManagementDemo() {
const form = useZodForm({
schema: profileSchema,
defaultValues: {
name: "John Doe",
email: "john@example.com",
bio: "Full-stack developer passionate about building great products",
},
})
const onSubmit = async (data: z.infer<typeof profileSchema>) => {
await new Promise((resolve) => setTimeout(resolve, 1500))
toast.success("Profile saved successfully!")
form.reset(data)
}
return (
<Card className="p-6">
<div className="mb-6">
<h3 className="text-lg font-semibold">Form Management</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} onSubmit={onSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input id="name" {...form.register("name")} />
{form.formState.errors.name && (
<p className="text-destructive text-sm">
{form.formState.errors.name.message}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" {...form.register("email")} />
{form.formState.errors.email && (
<p className="text-destructive text-sm">
{form.formState.errors.email.message}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="bio">Bio</Label>
<Textarea
id="bio"
{...form.register("bio")}
placeholder="Tell us about yourself"
rows={3}
/>
{form.formState.errors.bio && (
<p className="text-destructive text-sm">
{form.formState.errors.bio.message}
</p>
)}
</div>
<FormAutoSaveStickyBar />
</FormManagement>
</Card>
)
}
Installation
1. Install the component
pnpm dlx shadcn@latest add https://ui.nowts.app/r/form-management.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
Usage
Basic form with sticky bar
import { useForm } from "react-hook-form"
import {
FormAutoSave,
FormAutoSaveStickyBar,
} from "@/lib/form-management/form-management"
import { Input } from "@/components/ui/input"
export function BasicForm() {
const form = useForm({
defaultValues: { name: "", email: "" },
})
const onSubmit = async (data: any) => {
await saveUserData(data)
}
return (
<FormAutoSave form={form} onSubmit={onSubmit}>
<div className="space-y-4">
<Input {...form.register("name")} placeholder="Name" />
<Input {...form.register("email")} placeholder="Email" />
</div>
<FormAutoSaveStickyBar />
</FormAutoSave>
)
}
Form with auto-save
import {
FormAutoSave,
FormAutoSaveWatch,
} from "@/lib/form-management/form-management"
export function AutoSaveForm() {
const form = useForm()
const onSubmit = async (data: any) => {
await autosaveData(data)
}
return (
<FormAutoSave form={form} onSubmit={onSubmit}>
<div className="space-y-4">
<Input {...form.register("title")} placeholder="Document title" />
<Textarea
{...form.register("content")}
placeholder="Document content"
/>
</div>
{/* Auto-save every 3 seconds */}
<FormAutoSaveWatch form={form} autoSaveMs={3000} />
</FormAutoSave>
)
}
Custom sticky bar labels
<FormAutoSaveStickyBar actionLabel="Publish Changes" cancelLabel="Discard" />
Form with validation
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
const schema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
})
export function ValidatedForm() {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { name: "", email: "" },
})
const onSubmit = async (data: z.infer<typeof schema>) => {
await createUser(data)
}
return (
<FormAutoSave form={form} onSubmit={onSubmit}>
<div className="space-y-4">
<div>
<Input {...form.register("name")} placeholder="Name" />
{form.formState.errors.name && (
<p className="text-sm text-red-500">
{form.formState.errors.name.message}
</p>
)}
</div>
<div>
<Input {...form.register("email")} placeholder="Email" />
{form.formState.errors.email && (
<p className="text-sm text-red-500">
{form.formState.errors.email.message}
</p>
)}
</div>
</div>
<FormAutoSaveStickyBar />
</FormAutoSave>
)
}
Manual form actions
You can access form actions using the context:
import { useFormAutoSave } from "@/lib/form-management/form-management"
function CustomActions() {
const { isDirty, isLoading, submit, cancel } = useFormAutoSave()
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 FormAutoSave
;<FormAutoSave form={form} onSubmit={onSubmit}>
{/* form fields */}
<CustomActions />
</FormAutoSave>
API Reference
FormAutoSave
Prop | Type | Description |
---|---|---|
form | UseFormReturn | React Hook Form instance |
onSubmit | (data: T) => Promise<void> | Form submission handler |
autoSaveMs | number | Auto-save debounce in milliseconds |
action | string | Form action attribute |
FormAutoSaveStickyBar
Prop | Type | Default | Description |
---|---|---|---|
actionLabel | string | "Save" | Save button label |
cancelLabel | string | "Reset" | Cancel button label |
FormAutoSaveWatch
Prop | Type | Description |
---|---|---|
form | UseFormReturn | React Hook Form instance |
autoSaveMs | number | Auto-save debounce in milliseconds |
useFormAutoSave
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
}
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