Bootstrap 4


div is block element and by default it will be stacked. Bootstrap grid is mobile first, so three .col-md-4 will be stacked until desktop, so on desktop, large and extra large desktop it will be like three equal width columns. You do not need to define all 12, .col will occupy rest of place. When you specify for sm, you do not need to specify for md or lg. col-xs-12 is width: 100% which is default so no need to write that. Write only when you need columns.

Bootstrap Media queries:

  • xs - phones (no need for media queary since this is default)
  • sm - tablets @media (min-width: map-get($grid-breakpoints, 'sm') { // 576px (Bv3 768px) and up
  • md - desktops @media (min-width: @screen-md-min) { // 768px (Bv3 992px)
  • lg - larger desktops @media (min-width: @screen-lg-min) { // 992px (Bv3 1200px)
  • xl - extra lage @media (min-width: @screen-xl-min) { // 1200pxa

With bootstrap 4 you can use mixins for media queries:

@include media-breakpoint-up(sm) {
  .some-class {
    display: block;

// or other direction (given and smaller)
@include media-breakpoint-down(sm) {}

// only given size
@include media-breakpoint-only(sm) {}

// multiple sizes
@include media-breakpoint-between(md, xl) {}
@media (min-width: 768px) and (max-width: 1199.98px) { ... }

With Bootstrap3 you can use their predefined media queries with min-width (mobile first), for example

  .alert {
    position: absolute;
    bottom: 0px;
    right: 20px;

// or in other direction (given and smaller)
@media(max-width: $screen-sm) { }
// or better do not use boundary
@media(max-width: 575.98px) { }

Bootstrap container has fixed width for big devices.

Difference with bootstrap 3 is that there is new grid tier xl that was lg in B3 (sm is 576px instead of 768px in B3) and all are bumped up one level (so .col-md-6 in v3 is now .col-lg-6 in v4) Boostrap 3 you can use responsive available classes to toggle between display: block, inline, inline-block or to hide, for specific viewport size .visibe-sm-block .hidden-md .hidden-sm-up

In Bootstrap 4 you can use d-*-none class To hide on smaller than md (ie show on md and larger) use d-none d-md-block To show only on md use .d-none .d-md-block .d-lg-none

You can write your own to match all greater (or smaller) sizes:

# when it is max-width than it is Non-Mobile First Method
# so we use le (less or equal then)
# (min-width is Mobile First and we use hide-ge-)
/* Large Devices, Wide Screens */
@media only screen and (max-width : 1200px) {
  .hide-le-lg {
    display: none;
/* Medium Devices, Desktops */
@media only screen and (max-width : 992px) {
  .hide-le-md {
    display: none;
/* Small Devices, Tablets */
@media only screen and (max-width : 768px) {
  .hide-le-sm {
    display: none;
/* Extra Small Devices, Phones */
@media only screen and (max-width : 480px) {
  .hide-le-xs {
    display: none;
  • you can center bootstrap column with offset
  • you can set icons on inputs, easy with generators, just add prepend: 'password'

Bootstrap modal

You can use ajax, but than render layout: false and provide only inner html of <div class="modal"><div class="modal-dialog">... ie only .modal-content will be replaced. So you should use same header and footer in initial and ajax version.

To close modal with escape key you need to add tabindex on modal elemenent: <div class="modal" tabindex="-1">. Note that on Firefox ESC closing modal works without this tabindex attribute. Note also that select2 dows

On activation button you do not need data-keyboard="true" since that is default. But remove data-keyboard="false" if exists and you want to close modal with escape key.

You can use autofocus="true" option to focus input field when modal shows up.

For tooltips you can use html version data-html="true" data-toggle="tooltip" title="<h2>Hi</h2>".

If you use ajax than modal content is cached, you can clear that cache on shown or hidden event (or in ajax response).

$('#assign-location-modal').on('', function () {
# or for all, for example in
$(document).on '', '.modal', ->
  console.log "remove boostrap modal"

You can hide close modal with $('#my-modal').modal('toggle');

Usually fade makes some problem with positioning so you should not use fadding for custom modals.

Bootstrap tooltip

To inspect data-toggle="tooltip" you need to find it’s selector and manually call $('.myel').tooltip("show"); or in developer tools when you select <a title= element you can reference it with $0 so command is $($0).tooltip("show") or you can increase delay for hide

      hide: 100000

If it is inside element with overflow-y: scroll; than it won’t be seen if it goes over borders. You need to change container of the tooltip. You can do it simply for all tooptips by overwriting DEFAULTS (if you need more customization, you can overwrite any prototype of the class)

$.fn.tooltip.Constructor.DEFAULTS.container = 'body'
$.fn.tooltip.Constructor.DEFAULTS.placement = 'right'

Bootstrap input group and button group

Buttons and inputs can be grouped, but input group is not advised to group with select. Here is example of input with select and button.


Color helpers Primary colors are link primary, secondary, success, danger, warning, info, dark, light, white. For text there is muted and also need background text-light bg-dark and text-white bg-dark. You can set default body background by defining variable $body-bg: #bcdee3 before importing boostrap. You can use css darken for example for variable $link-hover-color: darken($link-color, 15%) !default;

  • text .text-success
  • background .bg-warning
  • buttons .btn-primary

Other helpers are:

  • position helpers position-relative, position-absolute
  • .clearfix
  • .text-center, .text-left, .text-nowrap
  • .show and .hidden
  • .d-none to hide something. But when I want to show/hide in js I usually create my own .d-none-not-important since bootstrap’s is with important. I used hide-not-important with jQuery $ (hide) or $el.slideDown() (slideUp) but they use display: block which does not work well with display: flex elements, better is to use $(el).addClass('d-none-not-important'). There is also hidden

    .d-none-not-important,.hide-not-important {
      display: none;
      &.active {
        display: initial;
  • margin and padding helpers in format <property><sides>-<breakpoint>-<size> property is m margin or p padding sides t top, b bottom, l left, r right, x both l and r, y t and b size 0, 1 ($spacer * 0.25), 2 ($spacer * .5), 3 ($spacer), 4 ($spacer * 1.5), 5 ($spacer * 3), By default $spacer: 1rem 1rem is 16px. mx-auto for margin: 0px auto.

    sides or breakpoint not required: p-0 padding none, mt-5 margin top, For fixed width block elements ml-auto to align to the right (same as pull-right in B3), mx-auto for horizontal centering. For inline elements you can use float-left (instead of pull-left). If you put inside d-flex than it will be scretched so use d-flex align-items-center to center verticaly.

  • d-flex class to convert to flexbox container. Other flex helpers for example to enable wrap use d-flex flex-wrap

  • dl-horizontal can be replaces with
    <dl class='row justify-content-start'>
      <dt class='col-sm-2'>Sign In Count</dt>
      <dd class='col'><%= @user.sign_in_count %></dd>
      <dt class='w-100'></dt>
      <dt class='col-sm-2'>Last Sign In at</dt>
      <dd class='col'><%= @user.last_sign_in_at.to_s :short %></dd>
      <dt class='w-100'></dt>


Bootstrap generators

This provides scaffold files

cat >> Gemfile << HERE_DOC
gem 'bootstrap-generators', '~> 3.3.4'
rails generate bootstrap:install --force # this will overwrite application.html.erb and
# generate lib/templates and stylesheets/boostrapp-generatora/variables.scss

git rm app/assets/stylesheets/application.css
cat > app/assets/stylesheets/application.scss << HERE_DOC
@import "bootstrap-generators";
git add app/assets/stylesheets/application.scss

Sass installation

This is original twitter bootstrap sass port

cat >> Gemfile << HERE_DOC
# twitter boostrap sass
gem "bootstrap-sass", '~> 3.3.7'

# cat > app/assets/stylesheets/application.scss << HERE_DOC
# // "bootstrap-sprockets" must be imported before "bootstrap" and "bootstrap/variables"
# @import "bootstrap-sprockets";
# @import "bootstrap";

# sed -i app/assets/javascripts/application.js -e '/jquery_ujs/a \
# //= require bootstrap-sprockets' # this is not needed since previous command
# will insert //= require bootstrap

# cat >> app/assets/stylesheets/application.scss << HERE_DOC
# // fixed navbar needs padding
# body {
#   padding-top: 60px;
# }

git add . && git commit -m "Adding boostrap"
# add devise links
cat > /tmp/template <<\HERE_DOC
        <ul class="nav navbar-nav">
          <% if current_user %>
            <li class="<%= 'active' if %(forms questions).include? params[:controller] %>">
              <%= link_to "My Forms", forms_path %>
          <% end %>
        <ul class="nav navbar-nav navbar-right">
          <% if current_user %>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><%= %> <span class="caret"></span></a>
              <ul class="dropdown-menu">
                <li><a href="#">Action</a></li>
                <li><a href="#">Another action</a></li>
                <li><a href="<%= edit_user_registration_path %>">Change password</a></li>
                <li role="separator" class="divider"></li>
                <li><a href="<%= destroy_user_session_path %>" data-method="delete">Sign out</a></li>
          <% else %>
            <li><a href="<%= new_user_registration_path %>">Sign up</a></li>
            <li><a href="<%= new_user_session_path %>">Log in</a></li>
            <li><%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %></li>
            <li><%= link_to "Sign in with Google", user_omniauth_authorize_path(:google_oauth2) %></li>
          <% end %>

sed -i app/views/layouts/application.html.erb -e '/<.ul>/ {
  r /tmp/template

Nabar shows different styles

Here is an example adding style for specific media for alert

// app/assets/stylesheets/common.scss
@import "bootstrap-variables";

.hidden-left {
  position: absolute;
  left: -1000px;

  .alert {
    position: absolute;
    top: 8px;
    padding: 8px;
    padding-right: 30px; // close sign
    z-index: 9999;
    left: 480px; // enought to see nav buttons

Still, default rails form build will render field_with_errors on label and input wrap, so if you need to change for bootstrap error classes. Note that with Bootstrap 3, you have to change control-group to form-group, add form-control to <input> elements, help-inline to help-block, and warning to has-warning or error to has-error. The easiest approach is with bootstrap form.

Bootstrap form

bootstrap_form gem rails-bootstrap-forms git (not old pluralize bootstrap_forms). Just add two lines

cat >> Gemfile << HERE_DOC
# adding bootstrap_form_for
gem 'bootstrap_form'

sed -i app/assets/stylesheets/application.css -e '1i\
 *= require rails_bootstrap_forms\
# or sass
cat >> app/assets/stylesheets/application.sass << HERE_DOC
// gems
@import 'rails_bootstrap_forms'

and use your bootstrap_form_for

You can use horizontal forms with layout: :horizontal (note that it won’t work for string layout: 'horizontal'). You can add options label_col: 'col-sm-4', control_col: 'col-sm-8'. Also you can override control_class, wrapper_class (if it is a hash you can override any option).

you can make checkbox inline with button

  <%= f.check_box :remember_me, inline: true %>
  <%= f.submit t "sign_in" %>

You can use form to have horizontal layout, and some input group can be inline, so you can group input and select (note that is you have horizontal layout you can not use this since col-sm-2 and col-sm-10 will be added, look below for custom form builder).

<%# app/views/todos/_form.html.erb %>
<%= bootstrap_form_for @task, url: todos_path do |f| %>
  <div class="form-inline">
    <%= f.label :name %>
    <div class="input-group input-group__with-select">
      <%= f.text_field :name, input_group_class: 'input-group-under-group-with-select', skip_label: true %>
      <%= :id, 1..5, skip_label: true %>
<% end %>
# app/assets/stylesheets/application.scss
.input-group__with-select {
  .input-group-under-group-with-select,.form-group {
    position: initial;
    display: initial;
    border-collapse: initial;
  .form-control {
    width: 50%!important;

You can prepend or append strings.

  <%= f.text_field :price, append: '$' %>

Checkboxes should be inside a form-group

  <%= f.form_group label: { text: 'Admin' } do %>
    <%= f.check_box :admin, label: '' %>
  <% end %>

Static test can be displayed with

<%= f.static_control :email %>
<%= f.static_control label: 'Custom Static Control' do %>
  Content here
<% end %>

You can use original rails form builder appending _without_bootstrap

  <%= f.text_field_without_bootstrap :name %>

Form builder

You can write your own form builder that extends for example rails-bootstrap-forms But since bootstrap_form_for (which is defined inside helper) calculate layout class (:inline or :horizontal) we need to use same helper (bootstrap_form_for ... builder: MyFormBuilder instead of form_for ... builder: MyFormBuilder). I noticed huge chrome memory problems (memory chrome is increasing when there is no form-horizontal class).

Here is example of input with select base od example

# app/form_builders/my_form_builder.rb
class MyFormBuilder < BootstrapForm::FormBuilder
  def my_text_field(method, options = {})
    content_tag :div, class: "my-wrapper" do
      super method, options
  def text_field_with_select(method_for_text_input, options_for_text_input, method_for_select, choices, options_for_select = {}, html_options = {})
    content_tag :div, class: "from-group form-group__with_select" do
      form_group_builder method_for_text_input, options_for_text_input do
        select_without_group = form_group_builder_without_group(method_for_select, options_for_select, html_options) do
          select_without_bootstrap(method_for_select, choices, options_for_select, html_options)
        text_field_without_bootstrap(method_for_text_input, options_for_text_input) +

  # this will overwrite all f.submit form helpers. data-disable-with does not
  # work well when responding with csv so there you should explicitly disable:
  # <%= f.submit "Generate CSV", 'data-disable-with': nil %>
  def submit(name, options = {})
    options.reverse_merge! 'data-disable-with': "<i class='fa fa-spinner fa-spin'></i> #{name}"
    hidden_field_tag(:commit, name) +
      button(name, options)

  def single_image_upload(method, options = {})


  def form_group_builder_without_group(method, options, html_options = nil)
    # copy from original form_group_builder
    # bootstrap_form-2.5.2/lib/bootstrap_form/form_builder.rb
    html_options.symbolize_keys! if html_options

    # Add control_class; allow it to be overridden by :control_class option
    css_options = html_options || options
    control_classes = css_options.delete(:control_class) { control_class }
    css_options[:class] = [control_classes, css_options[:class]].compact.join(" ")

    options = convert_form_tag_options(method, options) if acts_like_form_tag

    wrapper_class = css_options.delete(:wrapper_class)
    wrapper_options = css_options.delete(:wrapper)
    help = options.delete(:help)
    icon = options.delete(:icon)
    label_col = options.delete(:label_col)
    control_col = options.delete(:control_col)
    layout = get_group_layout(options.delete(:layout))
    form_group_options = {
      id: options[:id],
      help: help,
      icon: icon,
      label_col: label_col,
      control_col: control_col,
      layout: layout,
      class: wrapper_class

    if wrapper_options.is_a?(Hash)

    unless options.delete(:skip_label)
      if options[:label].is_a?(Hash)
        label_text  = options[:label].delete(:text)
        label_class = options[:label].delete(:class)
      label_class ||= options.delete(:label_class)
      label_class = hide_class if options.delete(:hide_label)

      if options[:label].is_a?(String)
        label_text ||= options.delete(:label)

      form_group_options.merge!(label: {
        text: label_text,
        class: label_class,
        skip_required: options.delete(:skip_required)
    form_group_without_group(method, form_group_options) do

  def form_group_without_group(*args, &block)
    options = args.extract_options!
    name = args.first

    options[:class] = ["form-group", options[:class]].compact.join(' ')
    options[:class] << " #{error_class}" if has_error?(name)
    options[:class] << " #{feedback_class}" if options[:icon]

    control = capture(&block).to_s
    control.concat(generate_help(name, options[:help]).to_s)
    control.concat(generate_icon(options[:icon])) if options[:icon]


Data confirm modal

When rails use data-confirm='Are you sure?' it opens default browser’s builtin confirm(). Instead of that, you can open bootstrap 3 or 4 modal with

  • navbar is starting class which display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between (so if you have two childs like navbar-brand and navbar-toggler they will be on left and right side, but navbar-collapse has flex-grow: 1; flex-basis: 100% so that’s why it looks like it’s on left, also navbar-expand-lg has justify-content: flex-start).
  • navbar-light bg-light to add colors (it will change color of nav-links)
  • navbar-collapse contains all navbar. add navbar-expand-lg on navbar so on lg navbar-toggler dissapears, navbar-collapse expands and navbar-nav change from column to flex-direction: row. Inside navbar-collapse beside navbar-nav you can also nest one more navbar-nav ml-auto if you want to pull some links to right.


<div class="card" style="width: 18rem;">
  <img class="card-img-top" src=".../100px180/" alt="Card image cap">
  <div class="card-body">
    <h5 class="card-title">Card title</h5>
    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>

Cards occupy full width, so you need to set width manually. card-body is used for padding.