Number fields with zodResolver
HTML inputs hold text. For a Zod number field, React Hook Form's valueAsNumber option converts the input before the resolver checks it.
All React Hook Form Resolvers examples React Hook Form Resolvers: usage and stats
How it works
- Register with valueAsNumber. Both inputs use
register('age', { valueAsNumber: true }), the same pattern as the Zod example in the resolvers docs. [1] - Check numbers with z.number().
agemust be at least 18 andseatsat least 1, each with a custom message. The empty seats field is not a number, so Zod reports its own error for it. [1] - Get typed values on submit. When both fields pass, the submitted object holds real numbers. The result line prints
typeof data.ageto show it. [2]
Run and edit it
It runs in your browser, in a sandboxed frame that cannot see this site; the packages load from esm.sh only after you press Run.
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
age: z.number().min(18, { message: 'You must be 18 or older' }),
seats: z.number().min(1, { message: 'Book at least 1 seat' }),
});
const label = { display: 'block', margin: '10px 0 4px', fontWeight: 600 };
const input = { width: '100%', boxSizing: 'border-box', padding: 8, borderRadius: 8, border: '1px solid #cbd5e1' };
const err = { color: '#dc2626', margin: '4px 0 0', fontSize: 13 };
export default function App() {
const [result, setResult] = useState(null);
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
defaultValues: { age: 16 },
});
const submit = handleSubmit((data) => setResult(data), () => setResult(null));
useEffect(() => { submit(); }, []); // validate once on load
return (
<div style={{ height: 360, background: '#111', display: 'grid', placeItems: 'center' }}>
<form onSubmit={submit} style={{ width: 340, padding: 20, borderRadius: 14, background: '#fff', color: '#111', font: '14px system-ui' }}>
<label style={label}>Age</label>
<input type="number" {...register('age', { valueAsNumber: true })} style={input} />
{errors.age?.message && <p style={err}>{errors.age?.message}</p>}
<label style={label}>Seats</label>
<input type="number" {...register('seats', { valueAsNumber: true })} style={input} />
{errors.seats?.message && <p style={err}>{errors.seats?.message}</p>}
<button type="submit" style={{ marginTop: 14, padding: '8px 16px', border: 0, borderRadius: 8, background: '#059669', color: '#fff', fontWeight: 600 }}>
Book
</button>
<p style={{ margin: '12px 0 0', color: '#475569', fontSize: 13 }}>
{result ? `Submitted ${JSON.stringify(result)} · typeof age: ${typeof result.age}` : 'Not submitted yet'}
</p>
</form>
</div>
);
}
The form validates on load; set age to 18+ and seats to 1+, then press Book. Code written by AgentGid [1].
The form validates on load; set age to 18+ and seats to 1+, then press Book. Code written by AgentGid [1].Checked Oct 12, 2026: it starts without errors in Chromium with @hookform/resolvers 5.9.1, react 19.3.0, react-dom 19.3.0, react-hook-form 7.90.0, zod 4.6.6. Needs WebGL.
More React Hook Form Resolvers examples
Zod validation with zodResolver Schema defaults with zodResolver Showing every error with criteriaMode Resolver options: raw values vs parsed values
Sources
Explanations written by AgentGid from the official docs; the numbers in brackets point here. The code marked "written by AgentGid" is ours and free to copy.