View

  • How to pass variables to template?
  • How to handle lists?
  • How to handle branching?
  • How to escape dangerous content?

Passing Variables

Take a look at lib/app1_web/controllers/page_html/home.html.heex. Replace it with following content:

<div>{ @message }</div>

Variables can be passed directly to render:

render(conn, :home, layout: false, message: "Hello")

You can also assign variables to conn:

conn
|> assign(:user, %{:name => "Alice"})
|> render(:home, layout: false)
<div>{@user.name}</div>

It is worth mentioning that assign is imported from Plug.Conn and it's immutable in behavior.

Render a list

<ul>
<%= for user <- @users do %>
  <li>{user.name}</li>
<% end %>
</ul>

Or use the :for attribute, which is more concise:

<ul :for={user <- @users}>
  <li>{user.name}</li>
</ul>

Branching

<%= if @logged_in do %>
  <p>Welcome back!</p>
<% else %>
  <p>Please log in.</p>
<% end %>

Elixir is more expressive than average programming languages, and it offers case and cond:

<%= case @user.role do %>
  <% "admin" -> %>
    <p>You're an admin.</p>
  <% "member" -> %>
    <p>You're a member.</p>
  <% _ -> %>
    <p>Unknown role.</p>
<% end %>
<%= cond do %>
  @user.age < 13 ->
    <p>You're a child.</p>
  @user.age < 20 ->
    <p>You're a teenager.</p>
  true ->
    <p>You're an adult.</p>
<% end %>

Escaping

Dangerous or untrusted content (like user input) must be escaped to prevent XSS attacks.

Phoenix's HEEx templates automatically escape content by default.

To render trusted content without excapping:

<%= raw("<b>Check you console!</b>") %>
<%= raw("<script>console.log('code executing!')</script>") %>

Layouts

Layouts are a way to define a shared HTML structure (like headers, navbars, footers) that wraps around your templates.

In lib/app1_web/components/layouts.ex, embed_templates is called to embed the template under the layouts folder as functions.

To render with the default layout:

render(conn, :home)

Custom Layout

Create a custom layout at lib/app1_web/components/layouts/admin.html.heex.

<header><h1>Admin Panel</h1></header>
<main><%= @inner_content %></main>
<footer><p>© 2025 Demo</p></footer>

The custom layout will also be wrapped with the root layout. So no need for a full html structure.

Specify the layout with put_layout:

conn
|> put_layout(html: :admin)
|> render(:home)

Component

<.button>Submit</.button>

Define custom component:

defmodule App1Web.CoreComponents do
  use Phoenix.Component

  def alert(assigns) do
    ~H"""
    <div class={"alert alert-#{@type || "info"}"}>
      <%= render_slot(@inner_block) %>
    </div>
    """
  end
end
<.alert type="error"><div>Something went wrong!</div></.alert>