How to mark optional form fields with Simple Form

Simple Form is a powerful gem for creating forms in Rails. For instance, Simple Form can look at your form object and display fields as “required” if they have :presence validations on them, rather than you hard-coding that in the markup.

By default, Simple Form will prepend required field labels with an asterisk. You can change the asterisk to something else (such as writing out the word “required”) in the simple_form.en.yml locale file. You can also move the required text to appear after the field label by changing the config.label_text option in the simple_form.rb initializer.

Tumblr inline nyswvtcy2k1qz7n3m 540

But what if you have a form where most fields are required, and you only want to mark the optional ones? You might be tempted to override the required fields by setting required: false and customize each optional field’s label by setting label: "Field name (optional)". This will give you the correct result, but it’s working against Simple Form and overriding a lot of behavior.

If you remove a :presence validation on a form object field, you will need to change the label on the form input. If you decide, at some future point, that you want to switch from denoting the optional fields to denoting the required ones, you’ll have to make changes to each input.

Here’s an alternative I came up with on a recent project.

First, I created a LabelNecessity module to extend Simple Form’s Labels module.

I’ve added default I18n values for the optional label text, mirroring the required label text from the Label module. I’ve also overridden the required_label_text method to show the required label text if the field is required, and the optional label text if the field is optional.

module SimpleForm
  module Components
    module LabelNecessity
      module ClassMethods #:nodoc:
        def translate_optional_html
          i18n_cache :translate_optional_html do
            I18n.t(:"simple_form.optional.html", default:
              %[<abbr title="#{translate_optional_text}">#{translate_optional_mark}</abbr>]
            )
          end
        end

        def translate_optional_text
          I18n.t(:"simple_form.optional.text", default: 'optional')
        end

        def translate_optional_mark
          I18n.t(:"simple_form.optional.mark", default: '*')
        end
      end

      protected

      def required_label_text #:nodoc:
        required_field? ? self.class.translate_required_html.dup : self.class.translate_optional_html.dup
      end
    end
  end
end

I added the following to simple_form.rb to extend the Labels module.

SimpleForm::Components::Labels.prepend SimpleForm::Components::LabelNecessity
SimpleForm::Components::Labels::ClassMethods.include SimpleForm::Components::LabelNecessity::ClassMethods

In my case, I wanted to write out the words “required” and “optional,” rather than using asterisks, so I updated the simple_form.em.yml file:

en:
  simple_form:
    required:
      html: '<small class="necessity">required</small>'
    optional:
      html: '<small class="necessity">optional</small>'

I also moved the required text to come after the label name, by updating the simple_form.rb initializer:

config.label_text = lambda { |label, required, explicit_label| "#{label} #{required}" }

Simple Form will now mark both required and optional fields. This may be fine, in some circumstances, but for more flexibility, I look to CSS. First, I add some styling to my .necessity class, so that it appears lighter and smaller than the label text.

label .necessity {
  font-size: smaller;
  font-weight: normal;
  color: #999;
}

By default, I want required fields to show the required label, but I want the optional fields to hide the optional label.

label.optional .necessity {
  display: none;
}

I want to be able to override this on a form-by-form basis, so I add classes that will show the optional labels and hide the required ones.

form.show-optional {
  label.optional .necessity {
    display: inline;
  }
}

form.hide-required {
  label.required .necessity {
    display: none;
  }
}

Now, by default, I’ll have required labels visible. If I want to also show optional labels, I can set the form class to show-optional:

<%= simple_form_for([@post], html: { class: "show-optional" }) do |f| %>

And if I want to only show the optional labels, I can set the form class to show-optional hide-required:

<%= simple_form_for([@post], html: { class: "show-optional hide-required" }) do |f| %>

Tumblr inline nyswxb9kwi1qz7n3m 540

The downside to this approach is that you’re creating markup that may often be hidden. But the upsides are that, accuracy is maintained (you’re not setting required: false, even when an input is truly required), and you have much less code to touch if you want to make changes to how you display required and optional fields.

Posted December 3, 2015 at 4:11 pm