Form Builder

In Rails, form builders work with ActiveRecord objects to make building forms easy. They handle things like:

  • Form authenticity tokens (for CSRF protection)
  • Filling fields with current values
  • Handling persistence (are we creating a new thing, or updating an old one?)
  • Nested objects

We've extended the default Rails form builder to output markup that matches our own form patterns. By using it we can:

  • Ensure consistency across our admin pages
  • Automatically display inline validation errors
  • Make it easier to update styles globally in future
  • Reduce markup clutter in templates

It's something of a work in progress, but the main features and patterns are documented below.

All individual field examples are written as if appearing within a form to build a new instance of class Person, which is a made-up model with a number of different attributes for illustrative purposes.

Starting a form

Forms are started using the form_for helper, wrapped in our own m-form molecule class. For our made-up model Person, we could write:

.m-form
  = form_for(person, builder: Admin::AdminFormBuilder) do |form|
    -# Form contents go here...

The rest of the examples on this page are presented as if nested in the above form.

Fieldsets and form groups

Fieldsets (m-form__fieldset) are used to group one or more fields. They provide a grey background and a bit of top and bottom padding:

= form.fieldset do
  -# 1 or more fields go here

Form groups (m-form__group) are, despite the name, usually wrapper classes for a single form input. They provide left and right padding, and our helper automatically applies an error modifier if validation errors are detected for the relevant field.

= form.form_group(:name) do
  -# The field for the "name" attribute goes here

Currently you have to add the group explicitly (as you'll see in later examples) - we plan to have them automatically generated in future.

Labels and help

Our helpers automatically apply a label to all types of field. If they detect that the field is required (e.g. it has a presence validation on it), it applies a "required" asterisk automatically.

= form.fieldset do
  = form.form_group(:name) do
    = form.text_field(:name)
  = form.form_group(:bio) do
    = form.text_field(:bio)

You can also override the label if desired:

= form.fieldset do
  = form.form_group(:name) do
    = form.text_field(:name, label: 'Your name')

Extra help can be added with the help attribute:

= form.fieldset do
  = form.form_group(:name) do
    = form.text_field(:name,
      label: 'Your name',
      help: 'The thing you say when people ask, "what are you called?"')

Validation and errors

The form builder handles detecting validation errors on specific fields, and renders the appropriate message(s) inline with the input element.

Note that for this to work, the field must be wrapped in a form_groupĀ with the appropriate field name.

= form.fieldset do
  = form.form_group(:name) do
    = form.text_field(:name)
  = form.form_group(:age) do
    = form.text_field(:age)

Text fields

Single line text fields use the text_field helper:

= form.fieldset do
  = form.form_group(:bio) do
    = form.text_field(:bio)

Multiple-line text fields use the text_area helper:

= form.fieldset do
  = form.form_group(:bio) do
    = form.text_area(:bio)

You can specify modifiers using the class attribute:

= form.fieldset do
  = form.form_group(:bio) do
    = form.text_area(:bio, class: 'a-input--textarea-tall')

Check boxes

Check boxes can be added with the check_box helper:

= form.fieldset do
  = form.form_group(:public_profile) do
    = form.check_box(:public_profile)
  = form.form_group(:receive_email) do
    = form.check_box(:receive_email)

Select boxes

Select boxes in Rails forms support a lot of options, and can be complicated to use. For full documentation of the different options, see the Rails docs.

We use a front-end library called Chosen to make our select boxes more manageable.

= form.fieldset do
  = form.form_group(:alignment) do
    = form.select(:alignment,
      options_for_alignment,
      { include_blank: true },
      { class: 'chosen-select-wide' })

Radio buttons

Radio buttons have their own type of form group:

= form.fieldset do
  = form.radio_button_group(:alignment) do
    = form.radio_button(:alignment, 'lawful-neutral', label: 'Lawful neutral')
    = form.radio_button(:alignment, 'chaotic-good', label: 'Chaotic good')

Secondary labels can also be applied:

= form.fieldset do
  = form.radio_button_group(:alignment) do
    = form.radio_button(:alignment, 'lawful-neutral',
      label: 'Lawful neutral',
      secondary_label: 'A lawful neutral character acts as law, tradition, or a personal code directs her. Order and organization are paramount to her.',
      label_class: 'u-bold')
    = form.radio_button(:alignment, 'chaotic-good',
      label: 'Chaotic good',
      secondary_label: "A chaotic good character acts as his conscience directs him with little regard for what others expect of him. He makes his own way, but he's kind and benevolent.",
      label_class: 'u-bold')

Action buttons

Action buttons can be added at the bottom of the form:

= form.fieldset do
  = form.form_group(:name) do
    = form.text_field(:name)
  = form.form_group(:age) do
    = form.number_field(:age)

.u-centered
  .m-button-group.m-button-group--horizontal
    = form.submit("Save")
    = form.cancel("Cancel", "#some-url-to-return-to")

Other field types

Rails form builders provide a number of other field types, some examples of which are shown below. More field types are supported by the form builder, but they don't all appear to be styled yet, so may or may not be usable.

= form.fieldset do
  = form.form_group(:age) do
    = form.number_field(:age, help: "This is a number field")
  = form.form_group(:password) do
    = form.password_field(:password, help: "This is a password field")
  = form.form_group(:birthday) do
    = form.date_field(:birthday, help: "This is a date field")