DEV Community

Cover image for How Layouts Work in Rails
Rails Designer
Rails Designer

Posted on • Originally published at railsdesigner.com

How Layouts Work in Rails

This article was originally published in Rails Designer.


Layouts in Rails have been around since the earlier versions. They define the surrounding HTML structure shared by multiple views, allowing you to implement a consistent visual design. They encapsulate shared site elements like partials and ViewComponent like: headers, footers, and navigation bars. This allows you have a consistent “chrome” for every view of your app, but also have invisible elements added onto every page, like JavaScript snippets.

Most Rails developers easily grasp the idea of layouts, but not all know the inner workings of layouts in Rails. Let's go over the most important parts.

How content is rendered in layouts

The central mechanism in layouts for embedding view-specific content is the yield method. When you define a layout, whether it's app-wide in application.html.erb or specifically for other controllers, yield marks the spot where content from individual view templates (show.html.erb, index.html.erb, etc.) will be inserted into the layout.

A typical layout might look like this:

<!DOCTYPE html>
<html>
<head>
  <title>Rails Designer</title>
</head>
<body>
  <header>
    <h1>Welcome to Rails Designer</h1>
  </header>

  <%= yield %>

  <footer>
    <p>© 2024 Rails Designer</p>
  </footer>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

In this layout, <⁠%= yield %> is where Rails inserts the content of other templates based on the action being executed.

How Rails looks up layouts

By default Rails looks for layouts in the app/views/layouts folder. The actual layout is checked in the following steps.

In your controller you can specify a layout:

class AuthorsController < ApplicationController
  layout "writers"
  # …
end
Enter fullscreen mode Exit fullscreen mode

Rails will check app/views/layouts for writers.html.erb file.

If no layout declaration was set in above controller, Rails uses the controller name to look for a authors.html.erb layout. If it cannot find a controller-specific layout, it will fallback to the default application.html.erb.

The layout declaration takes more than just a string or symbol though. You can also pass false to skip wrapping the view in a layout. Or you can pass a method with a conditional:

class AuthorsController < ApplicationController
  layout :authors_layout
  # …

  private

  def authors_layout
    Current.user.admin? ? "admins" : "authors"
  end
end
Enter fullscreen mode Exit fullscreen mode

You can also pass the only and except options to the layout declaration: layout :authors_layout, only: %w[index].

Layouts also cascade downward in the hierarchy. So with the following controller:

class Authors::BiographiesController < AuthorsController
  def show
  end
end
Enter fullscreen mode Exit fullscreen mode

The show view will also use the layout from the authors_layout method.

Are you using Devise? If you create a app/views/layouts/devise.html.erb layout, that layout is used for your signin and signup views. 💡

Nested layouts

Rails doesn't come with nested layouts out-of-the-box. If you are familiar with Static Site Generators you might have used them. I often use a nested layout for my SaaS' settings page. It often features a dedicated navigation for the many settings.

The cleanest solution, and the one I use in my Rails apps, works like this:

  1. Set the layout in the controller:
# app/controllers/settings_controller.rb
class SettingsController < ApplicationController
  layout "settings"
end
Enter fullscreen mode Exit fullscreen mode

All separate settings screens inherit from this controller, eg. Settings::TeamsController < SettingsController.

  1. Create a settings-layout:
# app/views/layouts/settings.html.erb
<div class="md:h-screen grid grid-cols-12 gap-4 md:gap-2 lg:gap-8">
  <%= component "navigation/settings", account: Current.account %>

  <div class="col-span-12 md:col-span-8 lg:col-span-9">
    <%= yield %>
  </div>
</div>

<⁠% parent_layout "application" %>
Enter fullscreen mode Exit fullscreen mode

You can add whatever component/html you need here. Make sure to include the yield method!

  1. Create a parent_layout helper

This helper is the important part that makes it nested layouts work.

# app/helpers/layouts_helper.rb
module LayoutsHelper
  def parent_layout(layout)
    @view_flow.set(:layout, output_buffer)

    output = render(template: "layouts/#{layout}")

    self.output_buffer = ActionView::OutputBuffer.new(output)
  end
end
Enter fullscreen mode Exit fullscreen mode

It saves the current output, renders the specified parent layout, and then sets this rendered output as the new output buffer to encapsulate nested layouts. That's all there's needed for nested layouts.

Is there anything else missing around layouts in this article? Let me know below!

Top comments (1)

Collapse
 
railsdesigner profile image
Rails Designer

What is something you learned from this article?