Subscribe to access all episodes. View plans →

#198: Multiple Layouts in Phoenix 1.8

Published August 18, 2025

Phoenix 1.8

Follow along with the episode starter on GitHub


It’s been a while since we looked at how to handle multiple layouts with Phoenix LiveView - and with the release of Phoenix 1.8, I thought it was time to take another look. In Phoenix apps, we’ve always had two main layouts. The root.html.heex static layout. This is the outermost layout that wraps everything in our application. and the app.html.heex layout used specifically for LiveViews.

We still have both of these layouts in Phoenix 1.8. But if you’re coming from earlier versions of Phoenix, you’ll notice the app.html.heex layout has been simplified. Instead of being a separate template file, it’s now a function component that lives right here in the Layout module. This is pretty cool because it means we can treat layouts just like any other component in our application!

Now, let’s take a quick look at our AlbumLive.Index module to see how this new layout system is being used. We’re explicitly calling the app layout as a function component with <Layouts.app>. It’s much more explicit and clear - you can see exactly which layout is being used just by looking at the render function.

Let’s see how we can create our own custom layout. We’re going to add a layout just for the AlbumLive.Show LiveView. This will help demonstrate how flexible this new system really is.

We’ll head back to the Layout module, and we’ll copy the app function and use it to create a new layout function named album_show_layout to make the Album show layout different. Let’s add a required background attribute. This means whenever we use this layout, we’ll need to provide a background color for it. And then we’ll replace the white background with the background assign, and since we’re doing interpolation with our background value, we’ll need to wrap this in curly braces to get it to work.

# lib/teacher_web/components/layouts.ex

...

attr :flash, :map, required: true, doc: "the map of flash messages"

attr :current_scope, :map,
  default: nil,
  doc: "the current [scope](https://hexdocs.pm/phoenix/scopes.html)"

attr :background, :string, required: true

slot :inner_block, required: true

def album_show_layout(assigns) do
  ~H"""
  <main class="min-h-screen px-4 py-8 sm:px-6 lg:px-8">
    <div class="mx-auto max-w-4xl">
      <div class={"#{@background} rounded-lg shadow-sm p-6 sm:p-8"}>
        {render_slot(@inner_block)}
      </div>
    </div>
  </main>

  <.flash_group flash={@flash} />
  """
end

...

With that, let’s call our new layout. We’ll open up the Show module, and we’ll need a background color to use. You could imagine an instance where maybe the background color is a user setting, being passed into the LiveView here, but let’s keep this simple and create a way to grab a TailwindCSS background color at random, and then we’ll include it in our assigns.

Now that we have a background to use, let’s update our HEEx template to call our new layout function album_show_layout, and we’ll need to include the new background attribute. And that’s it! Our album show LiveView has been updated to use our new layout.

# lib/teacher_web/live/album_live/show.ex

...

  @impl true
  def render(assigns) do
    background = Enum.random(["bg-orange-200", "bg-teal-200", "bg-rose-200", "bg-green-200"])
    assigns = assign(assigns, :background, background)
    
    ~H"""
    <Layouts.album_show_layout background={@background} flash={@flash}>
    ...
    </Layouts.album_show_layout>
    """
  ...
  end 

...

Let’s test it out. We’ll go to the command line and start our server.

$ mix phx.server
...

Now let’s check out our changes in the browser. Each time we visit an album’s show page, we see it using our custom layout with a randomly selected background color! The change to use function components with Phoenix 1.8 makes it much simpler to work with dynamic LiveView layouts.

© 2024 HEXMONSTER LLC