Forms

Approach

  • 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 are handled by htmx - all we have to do is to is add a loading spinner which is hidden by default. This pattern is described here.

Code Examples

A Basic Form

<%= 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") %>
  <% end %>
<% end %>
config/routes.rb
Rails.application.routes.draw do
  match  "/books/:id" => "books#edit", via: [:get,:patch], as: "book"
end
def edit
  @book = get_book 
  if request.post?
    if @book.update(book_params)
      redirect_to root_path
    end
  end
end

Loading Spinners

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

<%= 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 div with base styles' .ui-floating-input class

<%= 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.

<%= 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 %>

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.

class Order < ApplicationRecord
  accepts_nested_attributes_for :items 
end
<%= 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 %>
  • Read the full article which goes into more detail on nested forms.

Multi Step Forms

This is covered in detail here:

Multi Step & Nested Forms

Why not plain HTML?

If you've read HTML First, you'll know it recommends using as few abstraction layers as possible. Rails Form Helpers are one of the few times we recommend using an abstraction instead of just vanilla html <form> elements. This is because the utility they provide outweighs the drawbacks of the obfuscation they introduce.

  • 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.

Last updated