Fields & Binding
- Laying Out the Fields
- Common Field Components
- FDOs and Form Models
- The
useForm
Hook - Binding Fields
- Accessing the FDO
- Clearing the Form
- Reading from an FDO
This guide introduces the fundamentals of building forms and binding fields in Hilla. It covers how to lay out form fields, choose the appropriate input components, and use the useForm
hook to connect those components to your application’s data model. For more in-depth information about forms in Hilla, see the Hilla Reference Guide.
Tip
|
Part of a Series
This is part 1 of the Add a Form series. You should also read the Overview.
|
Laying Out the Fields
In Hilla, you build forms visually by adding input components to a layout component. The most common layout components for forms are Form Layout, Vertical Layout, and an ordinary <div>
(with some CSS). The Form Layout component supports multiple columns, whereas Vertical Layout lays out the components in a single column.
Tip
|
Layout or <div>?
If you are used to working with Flow, you can continue to work with the familiar layout components in Hilla. If you have a background in React or some other client-side framework, you may feel more comfortable working with <div> .
|
The following example shows how to build a two-column project proposal form with various input components:
import { ComboBox, DatePicker, FormLayout, TextArea, TextField } from "@vaadin/react-components";
import ProposalType from "Frontend/generated/com/example/application/domain/ProposalType";
export default function ProposalForm() {
return (
<FormLayout>
<TextField label="Title"/>
<ComboBox label="Proposal Type" items={Object.values(ProposalType)}/> 1
<TextArea label="Description" data-colspan="2"/> 2
<DatePicker label="Start Date"/>
<DatePicker label="End Date"/>
</FormLayout>
);
}
-
ProposalType
is anenum
in this example. -
Since
FormLayout
has two columns by default, this makes the description field take up the full width of the layout.
The finished form would look like this:

Form Layout is responsive — when space is limited, it automatically adjusts to a single-column layout, placing each input on its own row. It also includes additional features not covered in this example. For more information see the Form Layout documentation.
Common Field Components
Vaadin provides a set of built-in field components that you can use in your forms:
-
Text Input Components
-
Text Field — For standard single-line text input
-
Text Area — For multi-line text input
-
Email Field — Specialized field for email addresses
-
Password Field - Secure field for password entry
-
Rich Text Editor - Advanced text editing with formatting options
-
-
Selection Components
-
Checkbox — For binary (true/false) selections
-
Radio Button Group — For selecting a single option from a visible list
-
Combo Box — Drop-down for selecting a single option
-
Multi-Select Combo Box — Drop-down for selecting multiple options
-
Select — Simple drop-down menu
-
List Box — Scrollable list of options
-
-
Date and Time Components
-
Date Picker — For selecting a date
-
Time Picker — For selecting a time
-
Date Time Picker — For selecting both date and time
-
-
Numeric Components
-
Number Field — For numeric input
-
FDOs and Form Models
In Hilla, FDOs are fetched from and submitted to a browser-callable application service for further processing. FDOs are implemented in Java, typically as records, although JavaBeans are also possible.
Hilla automatically generates the necessary TypeScript interfaces and handles the serialization between Java and JSON. Hilla also generates a form model, which is a description of the FDO in TypeScript. This form model is then used by the Hilla binder.
Continuing with the earlier Project Proposal form example, you can create a record to represent the FDO:
public record Proposal(
@Nullable Long proposalId,
String title,
ProposalType type,
String description,
LocalDate startDate,
LocalDate endDate
) {
}
To get Hilla to find this FDO, you have to create a browser-callable service that either returns it or accepts it as a parameter:
@BrowserCallable
@AnonymousAllowed
public class ProposalService {
public void save(Proposal proposal) {
// Will be implemented later
}
}
For more information about browser-callable services, see the Add a Service guide.
The useForm
Hook
Hilla provides a useForm
hook that binds fields to form model properties, ensuring that changes made in the form update the FDO. When you implement your form as its own component, you should initialize it outside the form and pass the result in as a prop.
For example, here is a form component that accepts the result of useForm
as a required prop:
import { UseFormResult } from "@vaadin/hilla-react-form";
import ProposalModel from "Frontend/generated/com/example/application/tutorial/service/ProposalModel";
// (Other imports omitted for brevity)
export type ProposalFormProps = {
form: UseFormResult<ProposalModel> 1
}
export default function ProposalForm({form}: ProposalFormProps) {
return (
<FormLayout>
...
</FormLayout>
);
}
-
ProposalModel
is a Hilla form model generated from theProposal
FDO.
A parent component that uses the form could look something like this:
import { useForm } from "@vaadin/hilla-react-form";
// (Other imports omitted for brevity)
export default function ProposalDrawer() {
const form = useForm(ProposalModel);
return (
<section>
<h2>Edit Proposal</h2>
<ProposalForm form={form}/>
</section>
);
}
Binding Fields
To bind a field to a form model property, use the field
method from the useForm
hook. This method generates an object containing all necessary props (like value, event handlers, and validation state) for the field component. You then use React’s spread syntax ({…}
) to apply these props to the component.
When using the field()
method, you need to specify which property from the form model you want to bind by passing it as an argument. These properties are available in the model
object returned by the useForm
hook.
Here is an example that binds each field in the project proposal form to the corresponding form model property:
// (Imports omitted for brevity)
export type ProposalFormProps = {
form: UseFormResult<ProposalModel>
}
export default function ProposalForm({form}: ProposalFormProps) {
return (
<FormLayout>
<TextField label="Title"
{...form.field(form.model.title)}/>
<ComboBox label="Proposal Type"
items={Object.values(ProposalType)}
{...form.field(form.model.type)}/>
<TextArea label="Description"
data-colspan="2"
{...form.field(form.model.description)}/>
<DatePicker label="Start Date"
{...form.field(form.model.startDate)}/>
<DatePicker label="End Date"
{...form.field(form.model.endDate)}/>
</FormLayout>
);
}
Accessing the FDO
To access the FDO itself, use the value
variable returned by the useForm
hook:
const form = useForm(ProposalModel);
useEffect(() => {
// Do something whenever the user selects a different type.
}, [form.value.type]);
In the example above, value
is an instance of type Proposal
. Hilla makes sure the FDO and the form are always in sync.
Note
| In Hilla, there is always an FDO. If you haven’t initialized your form with an existing FDO, Hilla creates a new, empty one. |
Clearing the Form
To clear the form, the useForm
hook provides a clear()
method:
// (Imports omitted for brevity)
export default function ProposalDrawer() {
const form = useForm(ProposalModel);
return (
<section>
<h2>Edit Proposal</h2>
<ProposalForm form={form}/>
<Button onClick={form.clear}>Clear Form</Button>
</section>
);
}
Clearing the form also clears the FDO, including unbound properties.
Reading from an FDO
To populate a form with data from an existing FDO, the useForm
hook provides a read()
method:
// (Imports omitted for brevity)
export type ProposalDrawerProps = {
proposal?: Proposal
}
export default function ProposalDrawer({proposal}: ProposalDrawerProps) {
const form = useForm(ProposalModel);
useEffect(() => {
form.read(proposal);
}, [proposal]);
return (
<section>
<h2>Edit Proposal</h2>
<ProposalForm form={form}/>
</section>
);
}
Reading an undefined
or null
value clears the form.