Forms and validation
Production-grade SaaS UI skills for AI coding agents (React + Tailwind + shadcn/ui) — Codex, Claude Code, Cursor, OpenCode. One npx command to install.
npx -y skills add param087/saas-ui-skills --skill forms-and-validationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when building forms with react-hook-form + zod and shadcn/ui Form — schema validation, inline errors, accessible fields, async submit with loading and server errors, and avoiding uncontrolled-input bugs.
SKILL.md
3.9 KB, as published. Nobody here has run it
Forms & Validation
Overview
The reliable SaaS form stack is react-hook-form (state + performance) + zod (schema validation) + shadcn/ui Form (accessible field wiring). Define the schema once, infer the TypeScript type from it, and let the form derive validation, errors, and types from a single source of truth.
When to use
- Any form: auth, settings, create/edit dialogs, checkout.
- Inputs need validation, inline errors, or async submission.
- You're manually wiring
useStateper field (stop — use RHF).
The pattern
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
const schema = z.object({
email: z.string().email("Enter a valid email"),
password: z.string().min(8, "At least 8 characters"),
});
type Values = z.infer<typeof schema>;
export function SignInForm() {
const form = useForm<Values>({
resolver: zodResolver(schema),
defaultValues: { email: "", password: "" },
});
async function onSubmit(values: Values) {
const res = await signIn(values);
if (!res.ok) {
form.setError("password", { message: "Invalid email or password" });
return;
}
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4" noValidate>
<FormField control={form.control} name="email" render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl><Input type="email" autoComplete="email" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<FormField control={form.control} name="password" render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl><Input type="password" autoComplete="current-password" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<Button type="submit" disabled={form.formState.isSubmitting} className="w-full">
{form.formState.isSubmitting ? "Signing in…" : "Sign in"}
</Button>
</form>
</Form>
);
}
shadcn/ui Form auto-wires id, aria-invalid, aria-describedby, and <FormMessage> to each field — accessible errors with no manual plumbing.
Why this stack
- One schema → types + validation.
z.inferkeeps TS and runtime in sync. - Reuse on the server. Run the same zod schema in your API/action; never trust the client.
- Performance. RHF uses uncontrolled inputs + refs, so typing doesn't re-render the whole form.
Submission UX
- Disable submit + show a spinner label while
isSubmitting. - Map server/field errors with
form.setError(name, …); show form-level errors in anAlert. - On success: toast, then redirect or reset (
form.reset()).
Pitfalls
useStateper field — re-renders, more code, manual error wiring.- Always provide
defaultValues— omitting them makes inputs uncontrolled and React warns/loses data. - Validating only on the client — re-run the zod schema server-side.
- No submitting state — users double-submit; disable the button.
- Missing
autoComplete/type— hurts autofill and mobile keyboards. type="submit"missing — Enter won't submit the form.
Hand-off
A validated, accessible form that auth-screens, settings-pages, and onboarding-flows reuse; pair errors with notifications-and-toasts for success feedback.