Inputs
form.register(name) returns the props that bind one control to a field: its value, onChange, onFocus, onBlur, onKeyDown and aria-invalid. Spread them onto the element and the hook owns the rest.
<input {...form.register('email')} />The field must exist in fields; the type you declared there decides how the value is read. Every control is controlled: the value always comes from form.values.
By control
Section titled “By control”text, email, tel, url, number, password, date and textarea all bind the same way. The value is a string, including for number and date.
const form = useSnipForm({ key, fields: { name: { type: 'text', rules: { required: null } }, email: { type: 'email', rules: { required: null, email: null } }, age: { type: 'number', rules: { 'min[18]': 'You must be 18 or older' } }, starts: { type: 'date', rules: { 'after[today]': null } }, message: { type: 'textarea', rules: { 'max_length[2000]': null } }, },});
<input type="text" {...form.register('name')} /><input type="email" {...form.register('email')} /><input type="number" {...form.register('age')} /><input type="date" {...form.register('starts')} /><textarea rows={5} {...form.register('message')} />The HTML type attribute is yours to set; register does not add it. It only affects the browser’s keyboard and picker.
A single select is a string value. Use initial to preselect.
const form = useSnipForm({ key, fields: { plan: { type: 'select', initial: 'starter', rules: { 'in[starter,pro,team]': 'Pick a plan' } }, },});
<select {...form.register('plan')}> <option value="starter">Starter</option> <option value="pro">Pro</option> <option value="team">Team</option></select>For a placeholder option, leave initial unset (the value starts as '') and add required:
<select {...form.register('plan')}> <option value="">Choose a plan</option> <option value="pro">Pro</option></select>Declare the type as select-multiple. register adds multiple and the value is an array of the selected option values.
const form = useSnipForm({ key, fields: { tags: { type: 'select-multiple', initial: ['react'] }, },});
<select {...form.register('tags')}> <option value="react">React</option> <option value="vue">Vue</option> <option value="svelte">Svelte</option></select>One field, one input per option. Pass each option’s value to register; the field’s value is the array of checked options, in the order they were checked.
const form = useSnipForm({ key, fields: { topics: { type: 'checkbox', rules: { required: 'Pick at least one topic' } }, },});
<label><input type="checkbox" {...form.register('topics', { value: 'news' })} /> News</label><label><input type="checkbox" {...form.register('topics', { value: 'offers' })} /> Offers</label><label><input type="checkbox" {...form.register('topics', { value: 'events' })} /> Events</label>register sets value and checked on each input. required fails on an empty array, which is what you want for “pick at least one”.
A terms box is a checkbox field with one input and no explicit value. The option value defaults to 'on', so the field is [] unchecked and ['on'] checked. Pair it with the accepted rule, which passes for on.
const form = useSnipForm({ key, fields: { agree: { type: 'checkbox', rules: { accepted: 'You need to accept the terms' } }, },});
<label> <input type="checkbox" {...form.register('agree')} /> I agree to the terms</label>One field, one input per option, a string value. Each input gets its option via value; register sets checked on the one that matches.
const form = useSnipForm({ key, fields: { size: { type: 'radio', initial: 'm', rules: { 'in[s,m,l]': null } }, },});
<label><input type="radio" {...form.register('size', { value: 's' })} /> Small</label><label><input type="radio" {...form.register('size', { value: 'm' })} /> Medium</label><label><input type="radio" {...form.register('size', { value: 'l' })} /> Large</label>A hidden field carries a value the visitor never edits. Set it with initial and render it with type="hidden", or skip the element entirely: the value is submitted either way because it lives in form.values, not in the DOM.
const form = useSnipForm({ key, fields: { source: { type: 'hidden', initial: 'pricing-page' }, email: { type: 'email', rules: { required: null, email: null } }, },});
<input type="hidden" {...form.register('source')} />To change it later (a campaign id read from the URL, for example), use setValue.
setValue
Section titled “setValue”For anything that is not a native control, a date picker, a combobox, a map pin, write the value yourself:
<DatePicker value={form.values.starts as string} onChange={(iso) => form.setValue('starts', iso)}/>setValue(name, value) accepts a string or a string array and marks the field as touched. It does not fire a focus or keystroke signal, so keep at least one real input with register in the form.
Conditional fields
Section titled “Conditional fields”The server freezes the field set when the session opens, so a field you only show sometimes must still be in fields. Render it conditionally; its value is simply '' (or []) when it was never shown, and a field with no rules accepts that.
const form = useSnipForm({ key, fields: { plan: { type: 'select', initial: 'personal' }, company: { type: 'text' }, // optional: only shown for business plans },});
{form.values.plan === 'business' && ( <input {...form.register('company')} placeholder="Company" />)}A conditionally shown field with a required rule fails when hidden. Make the rule conditional on the server instead by using a rule that tolerates empty, or leave it optional.
What register returns
Section titled “What register returns”interface FieldProps { name: string; value?: string | string[]; // option value for checkbox/radio, array for select-multiple checked?: boolean; // checkbox and radio only multiple?: boolean; // select-multiple only onChange: (event) => void; onBlur: (event) => void; // triggers blur validation when validateOn: 'blur' onFocus: (event) => void; onKeyDown: (event) => void; 'aria-invalid': boolean | undefined; // true while the field has errors}Add your own className, placeholder, id, autoComplete and so on alongside the spread. If you need to wrap a handler, call the original inside yours:
const email = form.register('email');
<input {...email} onBlur={(event) => { email.onBlur(event); track('email_blur'); }}/>