How Layouts Work in Rails
Tags: rails, ruby, webdev
Published: 2024-06-13 10:00:00

This article was originally published in [Rails Designer](https://railsdesigner.com/rails-layouts/).

---

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](https://railsdesigner.com/what-is-view-component/) 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:

```ruby
<!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>
```

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:

```ruby
class AuthorsController < ApplicationController
  layout "writers"
  # …
end
```
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:

```ruby
class AuthorsController < ApplicationController
  layout :authors_layout
  # …

  private

  def authors_layout
    Current.user.admin? ? "admins" : "authors"
  end
end
```

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:

```ruby
class Authors::BiographiesController < AuthorsController
  def show
  end
end
```

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

Are you using [Devise](https://github.com/heartcombo/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:

```ruby
# app/controllers/settings_controller.rb
class SettingsController < ApplicationController
  layout "settings"
end
```

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

2. Create a settings-layout:

```ruby
# 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" %>
```

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


3. Create a `parent_layout` helper

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

```ruby
# 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
```

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!