💡 Rails Designer has a collection of beautiful layouts ready for your Rails app. 👈
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>
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
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
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
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:
- Set the layout in the controller:
# app/controllers/settings_controller.rb
class SettingsController < ApplicationController
layout "settings"
end
All separate settings screens inherit from this controller, eg. Settings::TeamsController < SettingsController
.
- 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" %>
You can add whatever component/html you need here. Make sure to include the yield
method!
- 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
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!