Form contract / Server Actions
Server Actions
A hidden input carries the canonical value, so a form with no client state still submits the right thing.
No client component required
import { CurrencyInput } from "@/components/ui/currency-input"
export default function Page() {
return (
<form action={save}>
<CurrencyInput name="price" label="Price" currency="USD" required positive />
<button>Save</button>
</form>
)
}
async function save(data: FormData) {
"use server"
const price = Number(data.get("price")) // 129900 — an integer, not "1,299.00"
}How
When you pass name, the component renders a hidden input carrying the serialised canonical value alongside the visible one. The visible input is never named, so FormData can only ever pick up the canonical value.
CardInput is the exception worth knowing: it takes name, expiryName and cvcName, because it is three values in one component.
Validate again on the server
Client-side constraints are a user-experience feature, not a security boundary. Re-check on the server with the Zod companion — see Zod companions. A dedicated @inputcn/server package that runs the identical validators without React is planned and not yet built.