Psst. It would be super cool if you could try the new Phoenix Boilerplate!

Try now

Tutorial

Nested Templates and Layouts in Phoenix Framework

templateslayoutsphoenix

Eventually when your site reach a certain maturity, you look into ways to refactor the code. One is to add parts of the templates in reusable layout templates. The reason is that HTML templates can be pretty verbose and you want certain elements to look the same across the site.

This approach is an alternative to using partials and even though partials are fine it might be a little cumbersome if you want to pass in a lot if content. Like a form or a large block of HTML.

STEP 1 - Create a shared components module

All the shared components will live in a module called SharedComponents. I usually add the view related helpers in the views folder.

# lib/sample_web/views/shared_components.ex
defmodule SampleWeb.SharedComponents do
  def card(assigns \\ %{}, do: block), do: render_template("card.html", assigns, block)

  defp render_template(template, assigns, block) do
    assigns =
      assigns
      |> Map.new()
      |> Map.put(:inner_content, block)

    SampleWeb.SharedView.render(template, assigns)
  end
end

But I also want to add an actual view file for rendering the templates:

# lib/sample_web/views/shared_view.ex
defmodule SampleWeb.SharedView do
  use SampleWeb, :view
end

STEP 2 - Add HTML templates

I will start by adding the card component. Note that since I using it as a block, everything inside the block will be outputted with the <%= @inner_content %>

<!-- /templates/shared/card.html.eex -->
<div class="max-w-sm rounded overflow-hidden shadow-lg">
  <div class="px-6 py-4">
     <%= @inner_content %>
  </div>
</div>

Moving on the the card header. Note that I also made it so I can pass in arguments when I call the <%= card_header do %> function. In this case, I might want some extra html class attributes.

<!-- /templates/shared/card_header.html.eex -->
<div class="bg-gray-50 py-4 px-6 border-b border-gray-200 rounded-t-lg <%= assigns[:class] %>">
  <%= @inner_content %>
</div>

And the same thing goes for the card body component.

<!-- /templates/shared/card_body.html.eex -->
<div class="px-8 py-8 <%= assigns[:class] %>">
  <%= @inner_content %>
</div>

STEP 3 - Start using the card component

With the templates setup, I should now be able to start using the cards in different templates. I can use only the outer card like this:

<%= card do %>
  <div class="font-bold text-xl mb-2">The Coldest Sunset</div>
  <p class="text-gray-700 text-base">
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.
  </p>
<% end %>

Or I can nest the components with card_header and card_body inside the card:

<%= card do %>
    <%= card_header do %>
      The Coldest Sunset
  <% end %>
  <%= card_body do %>
      <p class="text-gray-700 text-base">
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia,
      nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.
    </p>
  <% end %>
<% end %>

What are you working on?

If you want, you can send me a link to your Phoenix or Phoenix LiveView project so. Lets connect on Twitter or Linkedin.

- Andreas Eriksson, web developer since 2005

Related Tutorials

Published 11 Jul

Create a reusable modal with LiveView Component

tailwindalpinejsliveviewmodalphoenix

To reduce duplicity and complexity in your apps, Phoenix LiveView comes with the possibility to use reusable components. Each component can have its..

Published 30 Jul

Getting started with GraphQL and Absinthe in Phoenix

apiabsinthegraphqlphoenix

In the last tutorial, there I had an app with a simple rest api that was authenticated with Guardian and Json Web Token. In this tutorial, I will go..