Building your first form

How to build your Rails application with Simple Form and Bootstrap


Configuring your Rails app with Simple Form and Bootstrap:

  1. Create your new Rails application: rails new my_new_app
  2. Open the generated Gemfile and add the Simple Form dependency: gem 'simple_form'
  3. Install the dependencies: bundle install
  4. Install the Simple Form, using the --bootstrap option: rails generate simple_form:install --bootstrap
  5. Now you have a new Rails application using Simple Form and configured to use Bootstrap

Example

Lets build the following form.

Text input example
Password input example
File input example
Textarea input example
Date multi select example
Color *
Collection as inline radio buttons example
Fruit *
Collection as inline check boxes example
Collection multiple select example
Collection select example
Pill
Collection as radio buttons example
Choises *
Collection as check boxes example
Integer input example
Integer range example
:
Time multi select example
:
Datetime multi select example
Active *
Boolean as radio button example
Boolean as check box example

The code is straightforward and looks like this:

<%= simple_form_for @user, url: examples_vertical_path do |f| %>
  <%= f.error_notification %>

  <%= f.input :name %>

  <%= f.input :email, input_html: { autocomplete: 'email' } %>

  <%= f.input :password, input_html: { autocomplete: 'current-password' } %>

  <%= f.input :avatar, as: :file, input_html: { accept: User::AVATAR } %>

  <%= f.input :bio %>

  <%= f.input :birthday %>

  <%= f.input :color, as: :radio_buttons, collection: User::COLOR, wrapper: :vertical_collection_inline %>

  <%= f.input :fruit, as: :check_boxes, collection: User::FRUIT, wrapper: :vertical_collection_inline %>

  <%= f.input :music, collection: User::MUSIC, input_html: { multiple: true } %>

  <%= f.input :language, collection: User::LANGUAGE, prompt: :translate %>

  <%= f.input :pill, as: :radio_buttons, collection: User::PILL %>

  <%= f.input :choises, as: :check_boxes, collection: User::CHOISES %>

  <%= f.input :friends %>

  <%= f.input :mood, as: :range %>

  <%= f.input :awake %>

  <%= f.input :first_kiss %>

  <%= f.input :active, as: :radio_buttons %>

  <%= f.input :terms %>

  <%= f.button :submit, class: "btn-primary" %>
  <%= f.button :button, "Cancel", type: "reset", class: "btn-outline-secondary" %>
<% end %>

The code is very simple, isn't it?

But it can get better! We can use the wrapper_mapping option to remove that wrapper duplication. This option receives a Hash containing an input type and the wrapper that will be used for all inputs with specified type.

Example:

{ string: :string_wrapper, boolean: :boolean_wrapper }

All the String inputs will now use the :string_wrapper, and the same applies to boolean fields, which will use the :boolean_wrapper for all its inputs. You can see more information about wrapper_mappings here.

<%= simple_form_for @user, url: examples_vertical_path,
  wrapper_mappings: {
    check_boxes:    :vertical_collection,
    radio_buttons:  :vertical_collection,
    file:           :custom_file,
    boolean:        :custom_boolean_switch
  } do |f| %>
  <%= f.error_notification %>

  <%= f.input :email %>

  <%= f.input :password %>

  <%= f.input :avatar, as: :file %>

  <%= f.input :pill, as: :radio_buttons,
    collection: ['Red', 'Blue'] %>

  <%= f.input :choices, as: :check_boxes,
    collection: [
      "Option one is this and thatβ€”be sure to include why it's great",
      "Option two can be something else and selecting it will deselect option one"] %>

  <%= f.input :terms %>

  <%= f.button :submit %>
<% end %>

Simple, right? You can see the code for the other examples on GitHub.


Styling

To perfectly match bootstrap output these additional styles are suggested.
Load simple_form helpers in e.g. application.scss.

πŸ“‚ app/assets/stylesheets/application.scss

$theme-colors: ( "bootstrap": #563d7c, "simpleform": #00617f );
@import "bootstrap";

// App sections
@import "application/breadcrumb";
@import "application/example";
@import "application/flash";
@import "application/footer";
@import "application/header";
@import "application/jumbotron";

// Components
@import "components/alert";
@import "components/icon";

// Syntax highlight
@import "dracula/pygments/dracula";

// simple_form helpers
@import "simple_form-bootstrap/simple_form-bootstrap";

πŸ“‚ app/assets/stylesheets/simple_form-bootstrap/_simple_form-bootstrap.scss

@import "form_abbr";
@import "form_button";
@import "form_collection_label";
@import "form_custom_checkbox_switch";
@import "form_floating_labels";
@import "form_multi_select";

πŸ“‚ app/assets/stylesheets/simple_form-bootstrap/_form_abbr.scss

abbr[title] {
  text-decoration: none;
}

πŸ“‚ app/assets/stylesheets/simple_form-bootstrap/_form_button.scss

// apply primary button style to buttons with only `btn` class
[class="btn"] {
  @extend .btn-primary;
}

πŸ“‚ app/assets/stylesheets/simple_form-bootstrap/_form_collection_label.scss

// vertical form
.form-check {
  > .collection_check_boxes,
  > .collection_radio_buttons {
    @extend .form-check-label;
    // fix missing space between input & label
    margin-right: .265625rem;
  }
}

// custom form
.custom-control {
  > .collection_check_boxes,
  > .collection_radio_buttons {
    @extend .custom-control-label;
    // fix missing space between input & label
    margin-right: .265625rem;
  }
}

// custom form radio button fix
.form-group.radio_buttons {
  > .custom-control {
    @extend .custom-radio;
  }
}

// custom form check box fix
.form-group.check_boxes {
  > .custom-control {
    @extend .custom-checkbox;
  }
}

πŸ“‚ app/assets/stylesheets/simple_form-bootstrap/_form_custom_checkbox_switch.scss

// credits: http://wingman.mediumra.re/components-bootstrap.html#forms
.custom-control.custom-checkbox-switch {
  padding-left: 0;

  label {
    display: flex;

    &:before {
      top: 0;
      left: 0;
      position: relative;
      border-radius: $spacer !important;
      width: $spacer * 2;
      height: $spacer;
      transition: background-color .2s ease;
      display: inline-block;
      margin-right: $spacer / 2;
      align-self: center;
    }

    &:after {
      content: '';
      transition: left .2s ease;
      position: absolute;
      transform: scale(0.7);
      height: $spacer;
      width: $spacer;
      border-radius: 50%;
      left: 0;
      background: $white;
    }
  }

  input:checked {
    + label {
      background-image: none !important;

      &:after {
        left: $spacer;
      }
    }
  }
}

πŸ“‚ app/assets/stylesheets/simple_form-bootstrap/_form_floating_labels.scss

// extracted from:
// http://getbootstrap.com/docs/4.0/examples/floating-labels/

$sf-floating-input-padding-x: $input-padding-x !default;
$sf-floating-input-padding-y: $input-padding-y * 2 !default;

.form-label-group {
  position: relative;
  margin-bottom: 1rem;
}

.form-label-group > input,
.form-label-group > select,
.form-label-group > label {
  padding: $sf-floating-input-padding-y $sf-floating-input-padding-x;
}

.form-label-group {
  > label {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    width: 100%;
    margin-bottom: 0; /* Override default `<label>` margin */
    line-height: 1.5;
    color: #495057;
    border: 1px solid transparent;
    border-radius: .25rem;
    transition: all .1s ease-in-out;
    user-select: none;
  }

  select,
  input {
    &::placeholder {
      color: transparent;
    }

    &:not(:placeholder-shown) {
      padding-top: $sf-floating-input-padding-y + $sf-floating-input-padding-y * (2 / 3);
      padding-bottom: $sf-floating-input-padding-y / 3;

      ~ label {
        padding-top: $sf-floating-input-padding-y / 3;
        padding-bottom: $sf-floating-input-padding-y / 3;
        font-size: 12px;
        color: #777;
      }
    }
  }

  .custom-select,
  select.form-control {
    font-size: inherit;
    padding-bottom: 2px;

    ~ label {
      width: auto;
      padding-right: 0;
      padding-bottom: 0;
    }
  }

  select[multiple] {
    ~ label {
      background-color: $input-bg;
      padding: 0 0 0 $sf-floating-input-padding-x;
      margin-top: $input-border-width;
      margin-left: $input-border-width * 2;
      width: 99%;
    }
  }
}

πŸ“‚ app/assets/stylesheets/simple_form-bootstrap/_form_multi_select.scss

.custom-select,
.form-control {
  &.date,
  &.datetime,
  &.time {
    &:first-of-type { margin-left: 0 !important; }
    &:last-of-type { margin-right: 0 !important; }
  }
}

.custom-select {
  &[multiple],
  &:only-child {
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
}