Bootstrap doesn’t do much to naked elements in your markup. It sets the default font and makes a few color changes, but most of what Bootstrap does requires you to add classes to certain elements in a particular way. This means you’ll need access to the markup before you get started.
You might recall that you didn’t write any markup for the login screens—they were all provided by Devise. Devise is packaged as a Rails Engine,[23] so the gem itself contains the views. But it also contains a generator called devise:views that will extract those views into our application, allowing us to modify them.
| $ bundle exec rails generate devise:views |
| invoke Devise::Generators::SharedViewsGenerator |
| create app/views/devise/shared |
| create app/views/devise/shared/_links.html.erb |
| invoke form_for |
| create app/views/devise/confirmations |
| create app/views/devise/confirmations/new.html.erb |
| create app/views/devise/passwords |
| create app/views/devise/passwords/edit.html.erb |
| . . . |
| invoke erb |
| create app/views/devise/mailer |
| create app/views/devise/mailer/confirmation_instructions.html.erb |
| create app/views/devise/mailer/reset_password_instructions.html.erb |
| create app/views/devise/mailer/unlock_instructions.html.erb |
Now that you can edit these files, you can use Bootstrap’s CSS classes to make them look how you’d like.
Since we’d like to style both the login screen and the registration screen, we need a way to log ourselves out so we can see them. Devise set up all the necessary routes for us, so we just need to create a link to the right path in app/views/dashboard/index.html.erb:
| <header> |
| <h1> |
| Welcome to Shine, <%= current_user.email %> |
| </h1> |
| <h2> |
| We're using Rails <%= Rails.version %> |
| </h2> |
| </header> |
| <section> |
| <p> |
| Future home of Shine's Dashboard |
| </p> |
» | <%= link_to "Log Out", destroy_user_session_path, method: :delete %> |
| </section> |
With that link in place, you can log out to see the screens you’re going to style. You’re just going to be using the styles Bootstrap provides—you aren’t writing any CSS yourself. You’ll be amazed at how much better our screens are with just these simple changes.
First, you need to make sure all of your markup is in one of Bootstrap’s “containers,” which will “unlock” many of the features you need. You can apply this to the body element in your application layout:
» | <body class="container"> |
| <%= yield %> |
| </body> |
Reloading the app, you can see that this class added some sensible margins and padding:
Let’s start with the login screen.
Because Devise uses Rails’s RESTful routing scheme, the resource around logging in is called a “user session.” Therefore, the view for the login screen is in app/views/devise/sessions/new.html.erb.
For styling forms, Bootstrap’s documentation[24] has several different options. We’ll use the first, most basic one, which is perfect for our needs.
We’ll wrap each label and input element in a div with the class form-group and then add the class form-control to each control. The check box requires slightly different handling. We put it inside its own label, which is inside an element with the class checkbox. We also need the submit tag to look like a button, so we’ll use the btn class for that. We also want the button to be larger and more prominent. We can achieve that by using the classes btn-lg and btn-primary, respectively. These classes alter anything with the btn class, and this pattern is used throughout Bootstrap.
Here’s what the revised template looks like:
| <header> |
| <h1>Log in</h1> |
| </header> |
| <%= form_for(resource, as: resource_name, |
| url: session_path(resource_name)) do |f| %> |
| <div class="form-group"> |
| <%= f.label :email %> |
| <%= f.email_field :email, autofocus: true, class: "form-control" %> |
| </div> |
| <div class="form-group"> |
| <%= f.label :password %> |
| <%= f.password_field :password, autocomplete: "off", |
| class: "form-control" %> |
| </div> |
| <% if devise_mapping.rememberable? -%> |
| <div class="checkbox"> |
| <label> |
| <%= f.check_box :remember_me %> Remember Me |
| </label> |
| </div> |
| <% end -%> |
| <%= f.submit "Log in", class: "btn btn-primary btn-lg" %> |
| <% end %> |
| <%= render "devise/shared/links" %> |
If you reload your browser, the form (shown next) now looks a lot better than before:
The spacing and vertical rhythm of the elements is more pleasing. The form controls feel more spacious and inviting. The “Log in” button looks more clickable. You’ll even notice a subtle animation and highlight when switching the active form field. And all we did was add a few classes to the markup!
Before moving on to the registration screen, there’s one more thing to fix here. If you submit the form without providing an email address or password, Devise sets an error message in the Rails flash.[25] We’re currently not displaying that anywhere.
We need to display it and style it in a way that allows users to easily see it. This will help users better understand when they’ve messed something up.
In addition to classes designed to work with existing HTML entities like forms, Bootstrap provides components, which are a set of classes that, when applied to an element (or set of elements), create a particular effect. For the flash, Bootstrap provides a component called an alert.[26]
Let’s display the flash using this component, which just requires using the class alert and then either alert-danger or alert-info, for the alert and notice flash messages, respectively.
| <body class="container"> |
» | <% if notice.present? %> |
» | <aside class="alert alert-info"> |
» | <%= notice %> |
» | </aside> |
» | <% end %> |
» | <% if alert.present? %> |
» | <aside class="alert alert-danger"> |
» | <%= alert %> |
» | </aside> |
» | <% end %> |
| <%= yield %> |
| </body> |
| </html> |
The markup got a bit more complex. We’re using aside instead of div since it’s more semantically correct. (Bootstrap doesn’t generally care which type of element styles are applied to.) We’ve also had to wrap each alert component in code to check whether that message was actually set. This is because even without content, the Bootstrap alert component will still show up visually and look strange.
Also note the similarities between these classes and the ones we used to style the button on the login form. Here, alert declares our markup as an alert, and then alert-danger modifies it in the same way that btn-lg does for a btn.
With that done, you can navigate to a page requiring login, provide incorrect login details, and see that the flash messages are styled appropriately. Users can now easily see their mistakes and understand their successes, as shown in the screenshots.
The only thing left to do is to style the registration page.
Devise refers to the resource for a user signing up as a registration, so the registration form is located in app/views/devise/registrations/new.html.erb. We’ll apply the same classes to this page that we did to the previous.
| <h2>Sign up</h2> |
| <%= form_for(resource, as: resource_name, |
| url: registration_path(resource_name)) do |f| %> |
| <%= devise_error_messages! %> |
| <div class="form-group"> |
| <%= f.label :email %> |
| <%= f.email_field :email, autofocus: true, class: "form-control" %> |
| </div> |
| <div class="form-group"> |
| <%= f.label :password %> |
| <% if @minimum_password_length %> |
| <em>(<%= @minimum_password_length %> characters minimum)</em> |
| <% end %> |
| <%= f.password_field :password, |
| autocomplete: "off", |
| class: "form-control" %> |
| </div> |
| <div class="form-group"> |
| <%= f.label :password_confirmation %> |
| <%= f.password_field :password_confirmation, |
| autocomplete: "off", |
| class: "form-control" %> |
| </div> |
| <%= f.submit "Sign up", class: "btn btn-primary btn-lg" %> |
| <% end %> |
| <%= render "devise/shared/links" %> |
Reload the page and navigate to the Sign Up screen as shown. You can see it’s now styled similarly to the login page:
Devise also provides screens for resetting your password and for editing your login details. I’ll leave that as an exercise for you to style those pages, but it will be just as simple as what you’ve seen already.
We now have a secure login system that looks great, and we’ve hardly written any code at all. We still have a few login requirements left to implement that aren’t provided by Devise by default. In the next section, you’ll see how to configure Devise to meet these requirements.