Forms
Lay out each form row with field, and connect the controls to form_with yourself. There is no form builder.
Wrap every control in ui_field
Don't build form rows out of divs and spacing utilities.
field already takes care of the label, the description, the error
message and the spacing between them.
All the field parts:
| Helper | Role |
|---|---|
ui_field |
The row. Takes orientation: :vertical (default), :horizontal or :responsive. |
ui_field_group |
Stacks several fields with consistent spacing. |
ui_field_set + ui_field_legend |
Groups related checkboxes or radios, instead of a div with a heading. |
ui_field_label |
The field's label. |
ui_field_title / ui_field_description |
Supporting text above and below the control. |
ui_field_error |
The error message, with role="alert". |
ui_field_content |
Wraps the control when it needs a container of its own. |
ui_field_separator |
Divides sections inside a group. |
Validation state
Pass invalid: true to ui_field. It sets
data-invalid and turns the row's text red (the destructive colour).
The message goes in ui_field_error.
ui_field_error takes either block content or an array in
errors:, and renders nothing when both are empty, so you can always
leave it in the template without an if.
There is no form builder integration
There is no f.ui_input. The controls pass any HTML attribute through
to the element, so you connect them to form_with yourself:
form.field_name and form.field_id produce the
user[email] and user_email names Rails expects, so
the params arrive as usual. The same works for
ui_checkbox, ui_switch,
ui_textarea, ui_select,
ui_radio_group and ui_slider.
Checkbox does not emit Rails' hidden input
form.check_box, it doesn't add a hidden field with the unchecked
value. Treat a missing parameter as unchecked, or add the hidden input yourself when the
form has to send a value either way:
Choosing the control
| Situation | Control |
|---|---|
| One line of text | ui_input |
| Several lines | ui_textarea |
| Text with a prefix, suffix or button | ui_input_group |
| One of a long list | ui_select, or combobox if people need to search it |
| One of a few visible options | ui_radio_group |
| The native picker on mobile, no JS | ui_native_select |
| Several independent choices | ui_checkbox |
| Two to seven compact choices | ui_toggle_group |
| A setting applied immediately | ui_switch |
| An approximate number | ui_slider |
| An exact number | ui_input(type: :number) |
| A date or a range | date-picker, or ui_calendar inline |
| A one-time code | ui_input_otp |
The date picker is a recipe
There is no Ui::DatePickerComponent. A date field is a
ui_popover with a ui_calendar inside, and the
ui-date-picker controller shows the chosen date on the trigger and
closes the popover. The calendar holds the value: give it name: and it
renders a hidden input, so the date is submitted like any other field.
The popover needs w-auto! p-0!, because its default
w-72 and padding leave too little room for the calendar. The
variations are calendar arguments:
mode: :range, caption_layout: :dropdown,
dir: :rtl.
bin/shadwire info date-picker lists them all, and the
Date Picker page
shows each one live.
Input group has its own input
Inside ui_input_group, use the group's own helpers instead of a plain
ui_input.