Text7 checks
FormEmail
Email with typo hints, domain checks and code verification.
Example
Installation
bunx --bun shadcn@latest add @fieldkit/form-emailNeeds the @fieldkit registry. Add it once
Usage
- 1
Add the field to your schema
schema.tsimport { z } from "zod"import { emailField } from "@/lib/field-schemas" export const schema = z.object({ email: emailField({ blockDisposable: true }),}) - 2
Render it inside a Form
The name prop matches the schema key.
example-form.tsximport { Form } from "@/components/form/form"import { FormEmail } from "@/components/form/form-email"import { FormSubmit } from "@/components/form/form-submit"import { FieldGroup } from "@/components/ui/field"import { schema } from "./schema" export function ExampleForm() { return ( <Form schema={schema} defaultValues={{ email: "" }} onSubmit={save} > <FieldGroup> <FormEmail name="email" /> <FormSubmit>Save</FormSubmit> </FieldGroup> </Form> )}
Checks
CheckOptionError message
Valid format
defaultEnter a valid email addressTypo suggestions
suggestDid you mean amina@gmail.com?No disposable domains
blockDisposableUse a permanent email addressNo free providers
blockFreeUse your work emailDomain accepts mail
checkDomain={fn}This domain cannot receive emailNot already registered
isFree={fn}This email already has an accountCode verification
verify={{ send, check }}That code is wrong. Check it or resend.On the server
Parse the same schema again on the server, and repeat the network checks there.
Terminal
const values = await signupSchema.parseAsync(await request.json())
if (!(await isEmailFree(values.email))) { throw new FormSubmitError({ fields: { email: "This email already has an account" } })}Best practices
- Verify with a code instead of asking for the email twice
- Lowercase the domain, keep the local part as typed
- Run network checks on blur, debounced 400 ms, never per keystroke
- Show typo fixes as a hint the user accepts, never auto-correct
- Parse the same schema on the server
Try these values
Terminal
emailField({ blockDisposable: true })| Input | Result | Output |
|---|---|---|
" Amina@Kijani.co.ke " | Valid | "amina@kijani.co.ke" |
"amina@" | Invalid | Enter a valid email address |
"amina@mailinator.com" | Invalid | Use a permanent email address |
"" | Invalid | Enter a valid email address |
API reference
FormEmail
PropDescription
verify{ send, check }Sends a 6-digit code and checks it. Submit waits until it is confirmed.isFree(email) => Promise<boolean>Returns true when no account uses the email. Runs on blur and submit, debounced 400 ms.checkDomain(domain) => Promise<boolean>Check the domain can receive mail.Field frame
PropDescription
namestringKey in the schema. Also picks the preset label and autocomplete.labelReact.ReactNodeOverrides the preset label. Pass null to hide it.descriptionReact.ReactNodeHint under the control.requiredbooleanOverrides the asterisk read from the schema.fieldPropsFormFieldPropsProps for the wrapping Field, such as className.