This article was originally published on [Rails Designer]https://railsdesigner.com/inline-form-validations/) — The First Professionally-designed UI Components Library for Rails
Validations have been a key part of Ruby on Rails. It provides an easy way to show any errors made by the user to the any inputs. Example of validation errors could be:
- Email has already been taken
- Password must be at least 8 characters long
- 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 (read this article about the various rendering strategies in ViewComponent:
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
methods.
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.
Top comments (1)
Anyone with deep a11y knowledge know about any (possible) downsides to this?