TeamComms Docs

Forms

describes how to create/use form and form elements.

Last modified: 11/8/2026

Forms

Teamcomms uses form and form elements from shadcn.

Steps

Follow this pattern:

Install prerequisities

Define form schema

This schema will be used in client side for forms and server side for procedures/server actions on server side.

// app/profile/schema.ts
import z from "zod";

export const updateProfileSchema = z.object({
  name: z
    .string()
    .min(3, { message: "Name must be 3 chars long." })
    .max(20, { message: "Name must be less than 20 chars." }),
  imageKey: z.string().optional(),
});
export type UpdateProfileType = z.infer<typeof updateProfileSchema>;

Initialize form

// app/profile/_components/update-profile-form.tsx
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

const form = useForm<UpdateProfileType>({
  resolver: zodResolver(updateProfileSchema),
  defaultValues: {
    name: session?.user?.name || "",
    imageKey:
      session?.user?.image && !session.user.image.includes("avatar.vercel.sh")
        ? session.user.image
        : "",
  },
  mode: "onChange",
});

Defile onShubmit function

// app/profile/_components/update-profile-form.tsx
function onSubmit(values: UpdateProfileType) {
  startTransition(async () => {
    const { data: result, error } = await tryCatch(); // trycatch hook
    // add server action to update profile inside trycatch
    if (error) {
      // show errors from server action
    }
    if (result.status === "success") {
      // do something
    }
  });
}

Create UI:

<form onSubmit={form.handleSubmit(onSubmit)}>
  <FieldGroup>
    {/* imported from shadcn field component */}
    <Controller
      name="name"
      control={form.control}
      render={({ field, fieldState }) => (
        <Field data-invalid={fieldState.invalid}>
          <FieldLabel htmlFor={field.name}>Name</FieldLabel>
          <Input
            {...field}
            id={field.name}
            placeholder="Your name"
            aria-invalid={fieldState.invalid && fieldState.isTouched}
          />
          <FieldDescription>
            This is your display name. Choose a readable name.
          </FieldDescription>
          {fieldState.invalid && <FieldError errors={[fieldState.error]} />}
        </Field>
      )}
    />
  </FieldGroup>
</form>

Built by Ali1raz. Source on GitHub.

On this page