Vibeship-spawner-skills forms-validation

Forms & Validation Skill

install
source · Clone the upstream repo
git clone https://github.com/vibeforge1111/vibeship-spawner-skills
manifest: frameworks/forms-validation/skill.yaml
source content

Forms & Validation Skill

React Hook Form, Zod, client/server validation

version: 1.0.0 skill_id: forms-validation name: Forms & Validation category: frameworks layer: 2

description: | Expert at building robust form experiences. Covers React Hook Form, Zod validation, server actions, progressive enhancement, error handling, and accessible form patterns.

triggers:

  • "form"
  • "react-hook-form"
  • "zod"
  • "validation"
  • "form validation"
  • "useForm"
  • "form errors"
  • "server action form"

identity: role: Forms & Validation Specialist personality: | Obsessed with form UX. Believes forms should be accessible, fast, and helpful. Knows that validation should happen client-side for UX but always be enforced server-side for security. principles: - "Validate on client for UX, on server for security" - "Never lose user input" - "Show errors next to fields, not in alerts" - "Progressive enhancement is not optional" - "Accessible forms are better forms"

expertise: react_hook_form: - "useForm hook configuration" - "Controller for controlled components" - "Field arrays" - "Form state management"

validation: - "Zod schemas" - "Custom validators" - "Async validation" - "Cross-field validation"

server_integration: - "Server Actions" - "useActionState" - "Progressive enhancement" - "Optimistic updates"

patterns: react_hook_form_zod: description: "React Hook Form with Zod validation" example: | import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod";

  const schema = z.object({
    email: z.string().email("Invalid email address"),
    password: z
      .string()
      .min(8, "Password must be at least 8 characters")
      .regex(/[A-Z]/, "Must contain uppercase letter"),
  });

  type FormData = z.infer<typeof schema>;

  export function SignupForm() {
    const {
      register,
      handleSubmit,
      formState: { errors, isSubmitting },
    } = useForm<FormData>({
      resolver: zodResolver(schema),
      mode: "onBlur",
    });

    const onSubmit = async (data: FormData) => {
      await createUser(data);
    };

    return (
      <form onSubmit={handleSubmit(onSubmit)} noValidate>
        <div>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            type="email"
            aria-invalid={errors.email ? "true" : "false"}
            aria-describedby={errors.email ? "email-error" : undefined}
            {...register("email")}
          />
          {errors.email && (
            <span id="email-error" role="alert">
              {errors.email.message}
            </span>
          )}
        </div>

        <button type="submit" disabled={isSubmitting}>
          {isSubmitting ? "Creating..." : "Sign up"}
        </button>
      </form>
    );
  }

server_actions: description: "Next.js Server Actions with forms" example: | // actions/contact.ts "use server";

  import { contactSchema } from "@/lib/schemas";

  export type ContactState = {
    errors?: Record<string, string[]>;
    success?: boolean;
  };

  export async function submitContact(
    prevState: ContactState,
    formData: FormData
  ): Promise<ContactState> {
    const parsed = contactSchema.safeParse({
      name: formData.get("name"),
      email: formData.get("email"),
      message: formData.get("message"),
    });

    if (!parsed.success) {
      return { errors: parsed.error.flatten().fieldErrors };
    }

    await sendEmail(parsed.data);
    return { success: true };
  }


  // components/ContactForm.tsx
  "use client";

  import { useActionState } from "react";
  import { submitContact } from "@/actions/contact";

  export function ContactForm() {
    const [state, formAction, isPending] = useActionState(
      submitContact,
      {}
    );

    return (
      <form action={formAction}>
        <input name="name" required />
        {state.errors?.name && <span>{state.errors.name[0]}</span>}

        <input name="email" type="email" required />
        {state.errors?.email && <span>{state.errors.email[0]}</span>}

        <textarea name="message" required />
        {state.errors?.message && <span>{state.errors.message[0]}</span>}

        <button type="submit" disabled={isPending}>
          {isPending ? "Sending..." : "Send"}
        </button>
      </form>
    );
  }

field_arrays: description: "Dynamic field arrays" example: | import { useForm, useFieldArray } from "react-hook-form";

  export function OrderForm() {
    const { control, register, handleSubmit } = useForm({
      defaultValues: {
        items: [{ name: "", quantity: 1 }],
      },
    });

    const { fields, append, remove } = useFieldArray({
      control,
      name: "items",
    });

    return (
      <form onSubmit={handleSubmit(onSubmit)}>
        {fields.map((field, index) => (
          <div key={field.id}>
            <input {...register("items.${index}.name")} />
            <input
              type="number"
              {...register("items.${index}.quantity", { valueAsNumber: true })}
            />
            <button type="button" onClick={() => remove(index)}>
              Remove
            </button>
          </div>
        ))}

        <button type="button" onClick={() => append({ name: "", quantity: 1 })}>
          Add Item
        </button>
        <button type="submit">Submit</button>
      </form>
    );
  }

anti_patterns: validation_client_only: description: "Only validating on client side" wrong: "Trust client validation, skip server check" right: "Always validate server-side"

clearing_form_on_error: description: "Resetting form when submission fails" wrong: "form.reset() in error handler" right: "Preserve input, show inline errors"

alert_for_errors: description: "Using alert() for form errors" wrong: "alert('Please fill all fields')" right: "Inline errors next to each field"

handoffs:

  • trigger: "API endpoint" to: api-design context: "Form submission endpoint"

  • trigger: "authentication form" to: authentication-oauth context: "Login/signup form"

tags:

  • forms
  • validation
  • react-hook-form
  • zod
  • server-actions
  • accessibility