Better Inline Validation for Rails Forms (with ViewComponent or partials)

Validations have been a key part of Ruby on Rails. It provides an easy way to show any errors made by the user to any of the inputs. Examples of validation errors could be:

  1. Email has already been taken;
  2. Password must be at least 8 characters long;
  3. Username can only contain letters, numbers, and underscores.

Typically these validation messages are shown as follows:

<% if @user.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@user.errors.count, "error") %> prohibited this <%= @user.class.name.downcase %> from being saved:</h2>

    <ul>
      <% @user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

While this does get the message across, it is better to place the validation messages close to input fields to enhance UX by providing immediate, contextual feedback and reducing cognitive load.

So what am I looking for? Something like this example:

It shows the input’s label by default, then replaces its text with the validation error message.

Let’s go over the steps to create this in your Rails app. 💡 No time to write this yourself? A FormLabelComponent is included in Rails Designer UI components library.

How to do this with ViewComponent

I assume you have a Rails app ready with ViewComponent installed. Generate the basic component: rails g component FormLabel --inline.

Then add the initializer passing along the FormBuilder object and the field name (eg. “email”).

class FormLabelComponent < ViewComponent::Base
  def initialize(form:, field:)
    @form = form
    @field = field
  end
end

There’s not much HTML needed for this component, so the HTML can be added inline:

class FormLabelComponent < ViewComponent::Base
  # …
  erb_template <<-ERB
    <%= @form.label @field, label_value, class: css %>
  ERB
  # …
end

It will create a typical label tag using the FormBuilder object (that you pass along; @form). Let’s look at label_value and css.

class FormLabelComponent < ViewComponent::Base
  # …
  private

  def label_value
    has_errors? ? field_error_message : @field.to_s.capitalize
  end

  def css
    # Tailwind CSS utility classes are used
    class_names(
      "text-sm text-gray-700 font-medium",
      {
        "text-red-600": has_errors?
      }
    )
  end
  # …
end

Looks like two more methods are used here: has_errors? and field_error_message. Have not seen class_names before? Read this article on lesser known Rails view helpers.

Let’s create those last two methods:

class FormLabelComponent < ViewComponent::Base
  # …

  # Using Ruby 3.0+ syntax for one-line methods
  def field_error_message = @form.object.errors.full_messages_for(@field).first

  def has_errors? = @form.object.errors.full_messages_for(@field).any?
end

The first method returns the first error message associated with a specific field (@field) in the form object.

And has_errors? checks if there are any error messages associated with a specific field (@field) in the form object. It returns true if there are errors, false otherwise.

You can then render it in your views like so: render FormLabelComponent.new(form: form, field: "email")

How to do this with Rails’ partials

But what if you don’t use ViewComponent? Well, first, do read this article on embracing ViewComponent when you come from partials (and helpers).

Let’s go over how this can be done with a small partial and helper. Not saying it is as pretty as the ViewComponent!

Create a partial. I’d typically would put it in app/views/components:

<%# locals: (form:, field:) %>

<%= form.label field, label_value(form, field), class: form_label_css(form, field) %>

Notice the use of strict locals here (introduced in Rails 7.1).

Then create a helper to store those label_value and form_label_css methods.

# app/helpers/form_label_helper.rb
module FormLabelHelper
  def label_value(form, field)
    has_errors?(form, field) ? field_error_message(form, field) : field.to_s.capitalize
  end

  def form_label_css(form, field)
    class_names(
      "text-sm text-gray-700 font-medium",
      {
        "text-red-600": has_errors?(form, field)
      }
    )
  end

  private

  def field_error_message(form, field) = form.object.errors.full_messages_for(field).first

  def has_errors?(form, field) = form.object.errors.full_messages_for(field).any?
end

In your views you can then render it like this: render "components/form_label", form: form, field: "email".

And that’s it. Two ways to improve the UX of your Rails app.

If you are in need of more UX and UI improvements, why not check out Rails Designer: it’s the first professionally-designed UI components library for Rails.

Get UI & Product Engineering Insights for Rails Apps (and product updates!)

Published at . Last updated at . Have suggestions or improvements on this content? Do reach out.

UI components for Ruby on Rails apps

$ 129 one-time
payment

Get Access
  • One-time Payment

  • Access to the Entire Library

  • Built using ViewComponent

  • Designed with Tailwind CSS

  • Enhanced with Hotwire