Skip to content

How To: Create custom layouts

Marco Colli edited this page Nov 30, 2023 · 22 revisions

If you would like to use a custom layout or layouts for Devise views, you have the following options:

  1. Use layout in individual Controllers
  2. Use layout and devise_controller? in ApplicationController
  3. Configure layouts for individual controllers in config/application.rb

Individual Controller

The easiest method is to provide your own controller as a subclass of a Devise controller and override the layout in the conventional Rails way:

# With custom single controller
class Users::RegistrationsController < Devise::RegistrationsController
  layout "devise", only: [:edit]
end

# then add this to your routes:
devise_for :users, controllers: { registrations: 'users/registrations' }
# With custom Devise parent controller
# config/initializers/devise.rb
config.parent_controller = 'MyBaseDeviseController'

# Controller
class MyBaseDeviseController < ApplicationController
  layout "devise"
end

ApplicationController

By using the devise_controller? helper you can determine when a Devise controller is active and override the layout accordingly:

class ApplicationController < ActionController::Base
  layout :layout_by_resource

  private

  def layout_by_resource
    if devise_controller?
      "devise"
    else
      "application"
    end
  end
end

You can build upon this to set a layout per role (or even per action). Below, resource_name is used to detect when Devise is handling an admin.

# Layout per resource_name
def layout_by_resource
  if devise_controller? && resource_name == :admin
    "layout_name_for_devise_admin"
  else
    "application"
  end
end

# Layout per resource_name AND action
def layout_by_resource
  if devise_controller? && resource_name == :user && action_name == "new"
    "layout_name_for_devise"
  else
    "application"
  end
end

Application / Devise Config

You can also set the layout for specific Devise controllers using a callback in config/application.rb. This needs to be done in a to_prepare callback because it's executed once in production and before each request in development.

This allows for layouts to be specified on a per-controller basis. If, for example, you want a specific layout assigned to Devise::SessionsController views:

config.to_prepare do
  # Configure single controller layout
  Devise::SessionsController.layout "layout_for_sessions_controller"

  # Or to configure mailer layout
  Devise::Mailer.layout "email" # email.haml or email.erb
end

If you want the same layout for all Devise views, except for when the user is editing its data, you could have something like this:

config.to_prepare do
  # Optional: Ensure that Routes and therefore Controller mixins like authenticate_user! are defined before autoloading Controllers
  Rails.application.reload_routes!

  Devise::SessionsController.layout "devise"
  Devise::RegistrationsController.layout proc{ |controller| user_signed_in? ? "application" : "devise" }
  Devise::ConfirmationsController.layout "devise"
  Devise::UnlocksController.layout "devise"            
  Devise::PasswordsController.layout "devise"        
end

If you prefer to put all config stuff in devise.rb config file:

# append to end of config/initializers/devise.rb
Rails.application.config.to_prepare do
  Devise::RegistrationsController.layout proc { |controller| user_signed_in? ? "application" : "devise" }
  # And/or Sessions, Confirmations, Unlocks, Passwords
end

Another approach to assigning the Devise Mailer layout is to change the config.parent_mailer from ActionMailer::Base to a mailer defined within the application, for example: ApplicationMailer.

Clone this wiki locally