---
title: content_for -- What is it good_for?
teaser: The Marie Kondo for bits of HTML that spark joy but have no home.
tags: ruby,rails,ruby on rails,accessibility,development
author: Louis Antonopoulos
published_on: 2025-03-24
---

Sometimes, you need to include HTML but you can't figure out where to do it,
especially if you're trying to push something *up* the view hierarchy.

The answer to your challenge is often
[#content_for](https://guides.rubyonrails.org/layouts_and_rendering.html#using-the-content-for-method).

Content what?

As described in the Rails guides:

>The content_for method allows you to insert content into a named yield block in your layout.

## How does it work?

Step 1: A view has a `#content_for` line or block. This is the "input".

```erb
<!-- app/views/posts/index.html.erb -->

<% content_for :title, "All the Posts" %>

<% content_for :something do %>
  <script>function hello() {}</script>
  <style>h1 {color: blue;}</style>
  I will appear in the :something block!
<% end %>
```

Step 2: The content in the line or block is picked up by a layout. You can either use
`content_for` again or `yield`. This is the "output".

```erb
<!-- app/views/layouts/application.html.erb -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <title><%= content_for(:title) || "My App Name" %></title>
  </head>

  <body>
    <%= content_for :something %>

    <!-- OR -->

    <%= yield :something %>
  </body>
```

The `content_for(:title)` in the default Application layout was added to Rails
at the beginning of
[2024](https://github.com/rails/rails/pull/49702).

Let's look at some other examples of how we can use `content_for`!

## Moving content out of a partial

Imagine we have a partial like this:

```erb
<!-- app/views/posts/_post.html.erb -->

<%= turbo_stream_from post %>

<li id="<%= dom_id(post) %>">
  <%= link_to post.title, post_path(post) %>
</li>
```

This will fail an
[accessibility audit](https://github.com/thoughtbot/capybara_accessibility_audit)
because the parent wraps the partial in a `<ul>` block, which
[only allows li, script, or template](https://dequeuniversity.com/rules/axe/4.1/list) tags,
and this will add a `<turbo-cable-stream-source>` element inside the `<ul>`.

We _could_ do this:

```erb
<li id="<%= dom_id(post) %>">
  <%= turbo_stream_from post %>

  <%= link_to post.title, post_path(post) %>
</li>
```

but that feels...icky, because the Turbo stream elements are spread out and
intermingled with our with UI content. Why should our `<li>` tags have extra
stuff in them?

Where the Turbo stream elements _really_ should go is in the `<body>` element
of the page. Let's put them away where they belong!

```erb
<!-- app/views/posts/_post.html.erb -->

<% content_for :body do %>
  <%= turbo_stream_from post %>
<% end %>

<li id="<%= dom_id(post) %>">
  <%= link_to post.title, post_path(post) %>
</li>
```

To support this, we must include the `content_for` in the layout:

```erb
<!-- app/views/layouts/application.html.erb -->

<body>
   <%= content_for :body %> <!-- or yield :body -->

   <% yield %>
</body>
```

Now our `<li>` elements are focused, and our `<body>` element has all the Turbo
refreshing elements grouped together.

## Overriding something at the top of the view hierarchy

Let's take a different example. Here, imagine our Application layout
specifies styling for the `<body>` element:

```erb
<!-- app/views/layouts/application.html.erb -->

<html lang="en">
  <head>
    <!-- head stuff -->
  </head>

  <body class="some_style">
    <main>
      <%= yield %>
    </main>
  </body>
</html>
```

What if we determine that some views need to amend or replace that style, but we don't
want to maintain an entirely different layout (or multiple layouts)
for that one customization?

If you guessed `#content_for`, you win...a new car! No. But you would if I had an
HTML game show and we asked that question.

So, what would this approach look like? (The HTML, not the game show.)

First, we update our layout file to accept a new `#content_for` block:

```erb
<!-- app/views/layouts/application.html.erb -->

<body class="some_style <%= content_for(:body_class) %>">
  <main>
    <%= yield %>
  </main>
</body>
```

And now, a view that wants to add extra styling way up there can do this:

```erb
<!-- app/views/gangnams/index.html.erb -->

<% content_for :body_class, "gangnam_style flex flex-row" %>
```

which will result in the `<body>` element rendering:

`<body class="some_style gangnam_style flex flex-row">`

<aside class="info">
Note that the call in the layout uses <code><%= content for ...</code> and the one
in the view uses <code><% content for ...</code> without the = sign. The reason is that
the view is calling the method, but the layout is writing the HTML from the call,
so it needs the =. If you use the = in both, that's ok, too.
</aside>

If we instead need to be able to _overwrite_ the parent style, we could leverage
`#presence` after the `#content_for` call:

```erb
<!-- app/views/layouts/application.html.erb -->

<body class="<%= content_for(:main_classes).presence || 'some_style' %>">
  <main>
    <%= yield %>
  </main>
</body>
```

so that if a view _doesn't_ specify `content_for :main_classes`, it will default to
`some_style`, but if it _does_ specify `:main_classes`, they will be the only classes
on the `<body>` element.

## Multiple `<main>` elements

In a view that incorporates an iframe of another view or site, it is quite
possible to end up with multiple `<main>` elements.

Like the `<li>` example, above, this will lead to an accessibility violation:
[the document has at most one main landmark](https://dequeuniversity.com/rules/axe/4.1/landmark-no-duplicate-main).

We _could_ set our `<main>` element to have a `title` property, but if we're
showing an iframe of our own site, that won't work, as the view and the iframe
will have the same value for the `title`.

Unless...

That's right! Unless we call on `#content_for` again:

```erb
<!-- app/views/layouts/application.html.erb -->

<body>
  <main title="<%= content_for(:main_title) %>">
    <%= yield %>
  </main>
</body>
```

Now our views can specify a unique title property
to make them more accessible.

## Selective Turbo morphing

Here's one last example before we go our separate ways today.

As I mentioned last week in
[Hotwire and That Syncing Feeling](https://thoughtbot.com/blog/hotwire-and-that-syncing-feeling#using-content_for-instead-of-an-application-wide-layout),
you may not want
[Turbo morphing](https://turbo.hotwired.dev/handbook/page_refreshes)
on every page.

In that case, we can use `#content for` to apply the morphing and scrolling helpers
to the `<head>` element of a particular view to request the behavior:

```erb
<!-- app/views/posts/index.html.erb -->

<% content_for :head do %>
  <%= turbo_refresh_method_tag :morph %>
  <%= turbo_refresh_scroll_tag :preserve %>
<% end %>
```

But wait, it gets better!

There's a helper that _already_ uses `#content_for :head` under the hood: `#turbo_refreshes_with`.
So if we do this instead:

```erb
<!-- app/views/posts/index.html.erb -->

<%= turbo_refreshes_with method: :morph, scroll: :preserve %>
```

it will automatically add the `turbo_refresh_method_tag` and `turbo_refresh_scroll_tag` elements
to the `<head>` element of that view, without us needing to explicitly call `#content_for`.

## Bringing it home

`#content_for` is the decluttering friend that solves HTML problems
with elegance and simplicity, leaving you with tidy layouts that spark joy.
You may not need it often, but when you do, it's the perfect tool for the job!
