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>&copy; 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.

Leave a Comment

Your email address will not be published. Required fields are marked *