Integrating with UI Libraries
In this guide, we'll walk through how to integrate Conform with custom input components.
#Concept
Conform supports native inputs out of the box by listening for form events like input
, focusout
, and reset
on the document. This means simple custom components that wrap native elements (e.g. a styled <input>
) typically work without extra setup.
However, more complex inputs like <Select />
, <DatePicker />
, or custom file uploaders often involve additional layers of abstraction. These layers interfere with how the browser emits native form events, making it harder for Conform to track user interactions.
To solve this, we provide the useControl
hook. It lets you manually connect a hidden base input to a custom UI component and dispatch native events in response to user interaction.
#Emulating Native Inputs
There are multiple ways to wire up a base input with useControl
:
In some cases, you may be able to re-use an input rendered by the UI library. This can help with progressive enhancement â for instance, when a user clicks on a styled label that toggles a real hidden checkbox using standard HTML behavior. The form still submits correctly even if JavaScript hasn't loaded.
But not all custom inputs enable this. Some UI libraries render hidden inputs and update their value via JavaScript after interacting with entirely separate elements. These setups won't work before JavaScript loads and may trigger their event handler in an unexpected order â for example, calling
onChange
before updating the input's value.Because
useControl
itself requires JavaScript, the benefits of re-using library-provided inputs are marginal. We recommend rendering your own base input unless you're confident it helps with progressive enhancement and behaves as expected.
The useControl
hook gives you a control object that manages the input value and provides methods to trigger form events. Here are the main steps to integrate it:
Register a base input
- Render a hidden native input element (e.g.
<input hidden />
) and register it withcontrol.register()
. - This serves as the authoritative source of the value and emits native form events.
- Render a hidden native input element (e.g.
Emit form events
- Use
control.change()
andcontrol.blur()
in your custom component'sonChange
andonBlur
handlers.
- Use
Make it controlled
- Use
control.value
,control.options
,control.checked
, orcontrol.files
to sync the custom component with the current state.
- Use
Delegate focus (optional)
- If your base input is hidden, use the
onFocus
callback inuseControl
to forward focus to the custom input for accessibility.
- If your base input is hidden, use the
Example
1import { useControl } from '@conform-to/react/future';
2import { useForm } from '@conform-to/react';
3import { Input } from 'custom-ui-library';
4
5function MyInput({ name, defaultValue }) {
6 const control = useControl({ defaultValue });
7
8 return (
9 <>
10 <input name={name} ref={control.register} hidden />
11 <Input
12 value={control.value}
13 onChange={(value) => control.change(value)}
14 onBlur={() => control.blur()}
15 />
16 </>
17 );
18}
Input Type Differences
Input types contribute to form data in different ways when it comes to form submission. Here's a quick reference:
Text Inputs: Straightforward. When empty, the value is an empty string.
Checkboxes / Radios:
- Default value is
'on'
unlessvalue
is specified. - If checked, the value is submitted; if unchecked, it's omitted.
- Default value is
Select (single): Value is the selected option's
value
. Defaults to the first option.Select (multiple): Represents an array of selected values. If none selected, no entry in
FormData
.File Inputs: Yields one or more
File
objects. If empty,FormData
omits the field.Other inputs: Behave like text inputs. Their
type
adds context but doesn't affect how data is submitted.
#Examples
We've prepared examples for integrating with popular UI libraries:
If the library you're using isn't listed or you run into issues, open a discussion â we're happy to help. Contributions are also welcome if you'd like to share an example.