RoR Layouts and Partials
Layouts and partials eliminate repeated HTML across your app. A layout wraps every page with a shared shell — the header, footer, and navigation. Partials extract reusable pieces of HTML into their own files so you write them once and reuse them anywhere.
Layouts
The default layout lives at app/views/layouts/application.html.erb. Every view renders inside this layout by default.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><%= content_for?(:title) ? yield(:title) : "MyApp" %></title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag "application" %>
<%= javascript_importmap_tags %>
</head>
<body>
<header>
<nav>
<%= link_to "Home", root_path %>
<%= link_to "Articles", articles_path %>
<% if current_user %>
<%= link_to "Logout", logout_path, method: :delete %>
<% else %>
<%= link_to "Login", login_path %>
<% end %>
</nav>
</header>
<% flash.each do |type, message| %>
<div class="flash-<%= type %>"><%= message %></div>
<% end %>
<main>
<%= yield %>
</main>
<footer>
<p>© 2024 MyApp</p>
</footer>
</body>
</html>
The yield keyword inserts the current action's view content. Everything else in the layout appears on every page.
Layout + View Diagram
Browser request: GET /articles Layout (application.html.erb) +----------------------------------+ | <header> nav links </header> | | | | <main> | | yield ← articles/index.erb | | <h1>All Articles</h1> | | <div>article cards</div> | | </main> | | | | <footer> ... </footer> | +----------------------------------+ Combined HTML sent to browser
Content Blocks — yield with a Name
Named yields let individual views inject content into specific slots in the layout — like a custom page title or page-specific scripts.
In the layout:
<title><%= yield(:title) || "MyApp" %></title>
<%= yield(:head_scripts) %>
In a specific view (articles/show.html.erb):
<% content_for :title, @article.title %>
<% content_for :head_scripts do %>
<script src="/javascripts/article_viewer.js"></script>
<% end %>
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
Custom Layouts per Controller
Different sections of your app can use different layouts:
class AdminController < ApplicationController layout "admin" ← uses app/views/layouts/admin.html.erb end class ApplicationController < ActionController::Base layout "application" ← default for all other controllers end
Partials
Partials are reusable HTML fragments. Their filenames start with an underscore, but you reference them without the underscore.
File: app/views/articles/_article.html.erb Reference: render "article" or render partial: "article"
Creating and Using a Partial
app/views/articles/_article.html.erb
<div class="article-card">
<h2><%= article.title %></h2>
<p><%= article.body.truncate(120) %></p>
<small>By <%= article.user.name %> on <%= article.created_at.strftime("%b %d, %Y") %></small>
<%= link_to "Read More", article_path(article) %>
</div>
app/views/articles/index.html.erb <h1>All Articles</h1> <% @articles.each do |article| %> <%= render "article", article: article %> <% end %>
Rendering a Collection with a Partial
Rails provides a shortcut when rendering a partial for each item in a collection:
<%= render @articles %>
Rails automatically looks for _article.html.erb and passes each article as a local variable named article. This one line replaces the entire each loop.
Shared Partials
Store partials used across multiple controllers in app/views/shared/:
app/views/shared/_flash_messages.html.erb app/views/shared/_navigation.html.erb app/views/shared/_footer.html.erb app/views/shared/_error_messages.html.erb
Render them from any view or layout:
<%= render "shared/flash_messages" %> <%= render "shared/navigation" %>
Passing Local Variables to Partials
Partial: app/views/shared/_button.html.erb
<%= link_to label, path, class: "btn #{style}" %>
Usage:
<%= render "shared/button", label: "Submit", path: articles_path, style: "btn-primary" %>
<%= render "shared/button", label: "Cancel", path: root_path, style: "btn-secondary" %>
Error Messages Partial
A common pattern is extracting error display into a shared partial:
app/views/shared/_error_messages.html.erb
<% if object.errors.any? %>
<div class="error-box">
<h4><%= object.errors.count %> error(s) found:</h4>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Include it in any form:
<%= render "shared/error_messages", object: @article %> <%= render "shared/error_messages", object: @user %>
Layouts and partials keep your views clean and consistent. When your design changes, you update one partial and the change propagates everywhere it is used.
