Rails Form Helpers

Rails Form Helpers are one of the few times we recommend using an abstraction instead of just vanilla html <form> elements. This is because they provide substantial utility:

  • Security: Every form includes and validates a CSRF protection.

  • Validation: We can show inline errors and top-of-form errors in one line of code.

  • Templating: We can use the same form template for both edit and update actions.

  • Populating: We don’t need to tell our form about existing record values as it can get these from our model.

  • Nested Forms: We can easily build has_many forms without creating a mess of frontend code.

Read More on Rails Form Helpers on the official Rails Site.

Form Styling

Form styling is powered by Base Styles. All we need to do is add a class of .ui-form to our form and all our form inputs will be styled for us. We can remove these default styles on a per field basis if we prefer - see instructions below.

Loading States

  • Form loading states are handled by htmx - all we have to do is to is add a loading spinner which is hidden by default. You can read more in our article on HTMX Indicators.

Code Examples

A Basic Form

Loading Spinners

Add an element with the .shown-while-loading class - it will be automatically shown when the form is submitted.

Loading Spinner
<%= form_with model: @book, url: book_path, html: {class:"ui-form"} do |f| %>
  <%= f.text_field :title, placeholder: "Title" %>
  <%= f.button class: "ui-button --solid" do |button| %>
    Save  
    <%= inline_svg_tag("heroicons/check.svg") %>
    <%= inline_svg_tag("misc/spinner.svg",class:"shown-while-loading") %>
  <% end %>
<% end %>

Floating Field Labels

Wrap an input and label in a dive with base styles’ .ui-floating-input class

Floating Inputs
<%= form_with model: @book, url: book_path, html: {class:"ui-form"} do |f| %>
  <div class="ui-floating-input">
    <%= f.text_field :title %>
    <%= f.label :title, class:"--label" %>
  </div>
  <%= f.button class: "ui-button --solid" do |button| %>
    Save  
    <%= inline_svg_tag("heroicons/check.svg") %>
  <% end %>
<% end %>

Show Errors

Use the shared/form_errors partial to display the errors for the form.

With Errors
<%= form_with model: @book, url: book_path, html: {class:"ui-form"} do |f| %>
  <%= render partial: "shared/form_errors", locals: { record: f.object} %>
  <div class="ui-floating-input">
    <%= f.text_field :title %>
    <%= f.label :title, class:"--label" %>
  </div>
  <%= f.button class: "ui-button --solid" do |button| %>
    Save  
    <%= inline_svg_tag("heroicons/check.svg") %>
    <%= inline_svg_tag("misc/spinner.svg",class:"shown-while-loading") %>
  <% end %>
<% end %>

Selects

We can implement clean selects with type to search, multi select and more, by wrapping a normal select in a <better-select> tag.

<%= form_with model: @book, url: book_path, html: {class:"ui-form"} do |f| %>
  <better-select>
    <%= f.select :category, options_for_select(Category.for_select) %>
  </better-select>
<% end %>

Read more about what’s possible in the Better Select Docs.

Forms with Has Many relationships

Sometimes we want to build experiences where users can create or edit a list of options in a single form. Rails form helpers can handle this with accepts_nested_attributes_for and fields_for.

In our model
class Order < ApplicationRecord
  accepts_nested_attributes_for :items 
end
In our view
<%= form_for @order, url: order_path(@order.shortcode) do |form| %>
  <%= form.fields_for :items do |item_fields| %>
    <%= item_fields.text_field :first_name %> 
  <% end %>
<% end %>

Multi Step Forms

  • There are several considerations when building Multi Step Forms, which we covered in our article: Multi Step & Nested Forms