Form

GitHub

Form wrapper for next-safe-action that uploads tracked files through a get-upload-url API route before submitting FormData.

Installation

bash
pnpm dlx shadcn@latest add @cs/form

Usage

tsx
import {
  Form,
} from "@/components/cs/form";
import { FormSubmit } from "@/components/cs/form-submit";
import { FormCheckbox } from "@/components/cs/form-checkbox";
import { FormDateInput } from "@/components/cs/form-date-input";
import { FormError } from "@/components/cs/form-error";
import { FormHidden } from "@/components/cs/form-hidden";
import { FormInput } from "@/components/cs/form-input";
import { FormOtpInput } from "@/components/cs/form-otp-input";
import { FormPasswordInput } from "@/components/cs/form-password-input";
import {
  FormRadioGroup,
  FormRadioGroupItem,
} from "@/components/cs/form-radio-group";
import { FormSelect } from "@/components/cs/form-select";
import { FormSwitch } from "@/components/cs/form-switch";
tsx
<Form action={saveProfile} uploadUrlApiPath="/api/file/upload-url">
  <FormInput name="name" label="Name" />
  <FormSubmit label="Save changes" />
</Form>

Verification code

Automatically submits a six-digit verification code and shows its value in a toast. Trying to submit a code other than "123456" will result in an error message.

tsx
"use client"

import { useRef } from "react"
import { Form } from "@/registry/cs/ui/form"
import { FormOtpInput } from "@/registry/cs/ui/form-otp-input"

import { actionResult } from "@/registry/cs/lib/action-result"

export default function FormAutoSubmitOtpPreview() {
	const codeRef = useRef("")

	return (
		<Form
			action={async () => ({
				data:
					codeRef.current == "123456"
						? actionResult(200, "Code accepted")
						: actionResult(400, "Invalid code"),
			})}
			autosave
			withoutRedirectBack
		>
			<FormOtpInput
				name="code"
				maxLength={6}
				onChange={(value) => {
					codeRef.current = value
				}}
			/>
		</Form>
	)
}

API Reference

DefaultFile

File metadata rendered as an attachment already stored by the application.

tsx
type DefaultFile = {
	/** Stable file identifier used as the rendered item key. */
	uuid: string
	/** File name displayed to the user. */
	name: string
	/** MIME type used to choose the attachment preview. */
	type: string
	/** Public or signed URL used to load the file preview. */
	src: string
}

CsFile

A file selected in the form and tracked through its upload lifecycle.

tsx
type CsFile = { file: File; hidden: boolean; state: "idle" | "error" | "done" | "uploading" | "processing" }

useFormContext

Returns the state and operations provided by the nearest Form.

Form

Wraps a next-safe-action form with pending state, validation results, file tracking, toast feedback, redirects, and optional autosave support. Files added through useFormContext().pushFile are uploaded before the server action runs. Configure uploadUrlApiPath with a GET route backed by getS3FileUploadUrl; the completed file UUIDs are appended to FormData as files entries.

Props

NameTypeDefaultDescription
action *FormAction<TData>-Server action with a Standard Schema input that receives the submitted FormData.
autosavebooleanfalseSubmits the form when submitAutosave is called.
children *React.ReactNode-Form fields and controls.
classNamestring-Additional classes applied to the field group.
initResultFormActionInitialResult<TData>-Initial next-safe-action result used before the first submission.
onSuccessCallback(() => void) | undefined-Called after a successful action result is received.
uploadUrlApiPathstring"/api/file/upload-url"Prepares a direct file upload. The default route is /api/file/upload-url.
withoutRedirectBackbooleanfalseDisables navigation back when a successful action has no redirect.