useControl
The
useControlhook is part of Conform's future export. These APIs are experimental and may change in minor versions. Learn more
A React hook that lets you sync the state of an input and dispatch native form events from it. This is useful when emulating native input behavior — typically by rendering a hidden base input and syncing it with a custom input.
For details on when you need this hook, see the UI Libraries Integration Guide.
import { useControl } from '@conform-to/react/future';
const control = useControl(options);#Options
defaultValue?: string | string[] | File | File[]
The initial value of the base input. It will be used to set the value when the input is first registered.
// e.g. Text input
const control = useControl({
defaultValue: 'my default value',
});
// e.g. Multi-select
const control = useControl({
defaultValue: ['option-1', 'option-3'],
});defaultChecked?: boolean
Whether the base input should be checked by default. It will be applied when the input is first registered.
const control = useControl({
defaultChecked: true,
});value?: string
The value of a checkbox or radio input when checked. This sets the value attribute of the base input.
const control = useControl({
defaultChecked: true,
value: 'option-value',
});onFocus?: () => void
A callback function that is triggered when the base input is focused. Use this to delegate focus to a custom input.
const control = useControl({
onFocus() {
controlInputRef.current?.focus();
},
});#Returns
A control object. This gives you access to the state of the input with helpers to emulate native form events.
value: string | undefined
Current value of the base input. Undefined if the registered input is a multi-select, file input, or checkbox group.
options: string[] | undefined
Selected options of the base input. Defined only when the registered input is a multi-select or checkbox group.
checked: boolean | undefined
Checked state of the base input. Defined only when the registered input is a single checkbox or radio input.
files: File[] | undefined
Selected files of the base input. Defined only when the registered input is a file input.
register: (element: HTMLInputElement | HTMLSelectElement | HTMLTextareaElement | Array<HTMLInputElement>) => void
Registers the base input element(s). Accepts a single input or an array for groups.
change(value: string | string[] | boolean | File | File[] | FileList | null): void
Programmatically updates the input value and emits both change and input events.
blur(): void
Emits blur and focusout events. Does not actually move focus.
focus(): void
Emits focus and focusin events. This does not move the actual keyboard focus to the input. Use element.focus() instead if you want to move focus to the input.
#Example
Checkbox / Switch
1import { useControl } from '@conform-to/react/future';
2import { useForm } from '@conform-to/react';
3import { Checkbox } from './custom-checkbox-component';
4
5function Example() {
6 const [form, fields] = useForm({
7 defaultValues: {
8 newsletter: true,
9 },
10 });
11 const control = useControl({
12 defaultChecked: fields.newsletter.defaultChecked,
13 });
14
15 return (
16 <>
17 <input
18 type="checkbox"
19 name={fields.newsletter.name}
20 ref={control.register}
21 hidden
22 />
23 <Checkbox
24 checked={control.checked}
25 onChange={(checked) => control.change(checked)}
26 onBlur={() => control.blur()}
27 >
28 Subscribe to newsletter
29 </Checkbox>
30 </>
31 );
32}Multi-select
1import { useControl } from '@conform-to/react/future';
2import { useForm } from '@conform-to/react';
3import { Select, Option } from './custom-select-component';
4
5function Example() {
6 const [form, fields] = useForm({
7 defaultValues: {
8 categories: ['tutorial', 'blog'],
9 },
10 });
11 const control = useControl({
12 defualtValue: fields.categories.defaultOptions,
13 });
14
15 return (
16 <>
17 <select
18 name={fields.categories.name}
19 ref={control.register}
20 multiple
21 hidden
22 />
23 <Select
24 value={control.options}
25 onChange={(options) => control.change(options)}
26 onBlur={() => control.blur()}
27 >
28 <Option value="blog">Blog</Option>
29 <Option value="tutorial">Tutorial</Option>
30 <Option value="guide">Guide</Option>
31 </Select>
32 </>
33 );
34}File input
1import { useControl } from '@conform-to/react/future';
2import { useForm } from '@conform-to/react';
3import { DropZone } from './custom-file-input-component';
4function Example() {
5 const [form, fields] = useForm();
6 const control = useControl();
7
8 return (
9 <>
10 <input
11 type="file"
12 name={fields.attachements.name}
13 ref={control.register}
14 hidden
15 />
16 <DropZone
17 files={control.files}
18 onChange={(files) => control.change(files)}
19 onBlur={() => control.blur()}
20 />
21 </>
22 );
23}#Tips
Progressive enhancement
If you care about supporting form submissions before JavaScript loads, set defaultValue, defaultChecked, or value directly on the base input. This ensures correct values are included in the form submission. Otherwise, useControl will handle it once the app is hydrated.
// Input
<input
type="email"
name={fields.email.name}
defaultValue={fields.email.defaultValue}
ref={control.register}
hidden
/>
// Select
<select
name={fields.categories.name}
defaultValue={fields.categories.defaultOptions}
ref={control.register}
hidden
>
<option value=""></option>
{fields.categories.defaultOptions.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
// Textarea
<textarea
name={fields.description.name}
defaultValue={fields.description.defaultValue}
ref={control.register}
hidden
/>Checkbox / Radio groups
You can register multiple checkbox or radio inputs as a group by passing an array of elements to register(). This is useful when the setup renders a set of native inputs that you want to re-use without re-implementing the group logic:
<CustomCheckboxGroup
ref={(el) => control.register(el?.querySelectorAll('input'))}>
value={control.options}
onChange={(options) => control.change(options)}
onBlur={() => control.blur()}
/>If you don't need to re-use the existing native inputs, you can always represent the group with a single hidden multi-select or text input. For complete examples, see the checkbox and radio group implementations in the React Aria example.