phoenix-views-templates
Render views and templates in Phoenix using HEEx templates, function components, slots, and assigns
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Phoenix Views and Templates
Phoenix uses HEEx (HTML+EEx) templates for rendering dynamic HTML content. HEEx provides compile-time validation, security through automatic escaping, and a component-based architecture. Views in Phoenix are modules that organize template rendering logic and house reusable function components.
View Module Structure
Phoenix view modules use the embed_templates macro to load HEEx templates from a directory:
defmodule HelloWeb.HelloHTML do
use HelloWeb, :html
embed_templates "hello_html/*"
end
This automatically creates functions for each .html.heex file in the hello_html/ directory.
HEEx Templates
Basic Template Structure
HEEx templates combine HTML with embedded Elixir expressions:
<section>
<h2>Hello World, from Phoenix!</h2>
</section>
Interpolating Dynamic Content
Use <%= ... %> to interpolate Elixir expressions into HTML:
<section>
<h2>Hello World, from <%= @messenger %>!</h2>
</section>
The @ symbol accesses assigns passed from the controller.
Multi-line Expressions
For expressions without output, omit the =:
<% # This is a comment %>
<% user_name = String.upcase(@user.name) %>
<p>Welcome, <%= user_name %>!</p>
Working with Assigns
Assigns are key-value pairs passed from controllers to templates:
# Controller
def show(conn, %{"messenger" => messenger}) do
render(conn, :show, messenger: messenger, receiver: "Dweezil")
end
<!-- Template -->
<section>
<h2>Hello <%= @receiver %>, from <%= @messenger %>!</h2>
</section>
All assigns are accessed with the @ prefix in templates.
Conditional Rendering
Using if/else
HEEx supports conditional rendering with if/else blocks:
<%= if some_condition? do %>
<p>Some condition is true for user: <%= @username %></p>
<% else %>
<p>Some condition is false for user: <%= @username %></p>
<% end %>
Using unless
For negative conditions:
<%= unless @user.premium do %>
<div class="upgrade-banner">
Upgrade to premium for more features!
</div>
<% end %>
Pattern Matching with case
For multiple conditions:
<%= case @status do %>
<% :pending -> %>
<span class="badge badge-warning">Pending</span>
<% :approved -> %>
<span class="badge badge-success">Approved</span>
<% :rejected -> %>
<span class="badge badge-danger">Rejected</span>
<% end %>
Looping and Iteration
For Comprehensions
Generate dynamic lists using for:
<table>
<tr>
<th>Number</th>
<th>Power</th>
</tr>
<%= for number <- 1..10 do %>
<tr>
<td><%= number %></td>
<td><%= number * number %></td>
</tr>
<% end %>
</table>
Iterating Over Collections
Loop through lists or maps:
<ul>
<%= for post <- @posts do %>
<li>
<h3><%= post.title %></h3>
<p><%= post.excerpt %></p>
</li>
<% end %>
</ul>
Shorthand :for Attribute
HEEx provides cleaner syntax for simple iterations:
<ul>
<li :for={item <- @items}><%= item.name %></li>
</ul>
Accessing Index
Get the iteration index with Enum.with_index/2:
<%= for {item, index} <- Enum.with_index(@items) do %>
<div class="item-<%= index %>">
<%= item.name %>
</div>
<% end %>
Function Components
Function components are reusable UI elements defined as Elixir functions that return HEEx templates.
Defining Function Components
Use the attr macro to declare attributes and the ~H sigil for the template:
defmodule HelloWeb.HelloHTML do
use HelloWeb, :html
embed_templates "hello_html/*"
attr :messenger, :string, required: true
def greet(assigns) do
~H"""
<h2>Hello World, from <%= @messenger %>!</h2>
"""
end
end
Using Function Components
Invoke components with the <.component_name /> syntax:
<section>
<.greet messenger={@messenger} />
</section>
Optional Attributes with Defaults
Define optional attributes with default values:
attr :messenger, :string, default: nil
attr :class, :string, default: "greeting"
def greet(assigns) do
~H"""
<h2 class={@class}>
Hello World<%= if @messenger, do: ", from #{@messenger}" %>!
</h2>
"""
end
Multiple Attribute Types
Components can accept various attribute types:
attr :title, :string, required: true
attr :count, :integer, default: 0
attr :active, :boolean, default: false
attr :user, :map, required: true
attr :items, :list, default: []
def card(assigns) do
~H"""
<div class={"card" <> if @active, do: " active", else: ""}>
<h3><%= @title %></h3>
<p>Count: <%= @count %></p>
<p>User: <%= @user.name %></p>
<ul>
<li :for={item <- @items}><%= item %></li>
</ul>
</div>
"""
end
Components with Computed Values
Use assign/2 to compute values within components:
attr :x, :integer, required: true
attr :y, :integer, required: true
attr :title, :string, required: true
def sum_component(assigns) do
assigns = assign(assigns, sum: assigns.x + assigns.y)
~H"""
<h1><%= @title %></h1>
<p>Sum: <%= @sum %></p>
"""
end
Slots
Slots allow components to accept blocks of content, enabling powerful composition patterns.
Defining and Using Slots
Define a slot and render it:
slot :inner_block, required: true
def card(assigns) do
~H"""
<div class="card">
<%= render_slot(@inner_block) %>
</div>
"""
end
Use the component with content:
<.card>
<h2>Card Title</h2>
<p>Card content goes here</p>
</.card>
Named Slots
Components can have multiple named slots:
slot :header, required: true
slot :body, required: true
slot :footer
def panel(assigns) do
~H"""
<div class="panel">
<div class="panel-header">
<%= render_slot(@header) %>
</div>
<div class="panel-body">
<%= render_slot(@body) %>
</div>
<%= if @footer != [] do %>
<div class="panel-footer">
<%= render_slot(@footer) %>
</div>
<% end %>
</div>
"""
end
Usage:
<.panel>
<:header>
<h2>Panel Title</h2>
</:header>
<:body>
<p>Panel content</p>
</:body>
<:footer>
<button>Close</button>
</:footer>
</.panel>
Slots with Attributes
Slots can accept attributes for more dynamic rendering:
slot :item, required: true do
attr :title, :string, required: true
attr :highlighted, :boolean, default: false
end
def list(assigns) do
~H"""
<ul>
<%= for item <- @item do %>
<li class={if item.highlighted, do: "highlight"}>
<%= item.title %>: <%= render_slot(item) %>
</li>
<% end %>
</ul>
"""
end
Rendering Child Templates
Rendering Other Templates
Include child templates within a parent:
<%= render("child_template.html", assigns) %>
Rendering Components from Other Modules
Call components from different modules:
<MyApp.Components.button text="Click me" />
Or with aliasing:
alias MyApp.Components
# In template:
<Components.button text="Click me" />
Layout Templates
Using Layouts
Layouts wrap rendered templates. Configure the layout in the controller:
def controller do
quote do
use Phoenix.Controller,
formats: [:html, :json],
layouts: [html: HelloWeb.Layouts]
...
end
end
Root Layout
The root layout includes the @inner_content placeholder:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>My App</title>
</head>
<body>
<%= @inner_content %>
</body>
</html>
App Layout Component
Nest layouts using components:
<Layouts.app flash={@flash}>
<section>
<h2>Hello World, from <%= @messenger %>!</h2>
</section>
</Layouts.app>
Disabling Layouts
Render without a layout:
def home(conn, _params) do
render(conn, :home, layout: false)
end
LiveView Integration
Delegating to Phoenix Views
LiveView can delegate rendering to existing view modules:
defmodule AppWeb.ThermostatLive do
use Phoenix.LiveView
def render(assigns) do
Phoenix.View.render(AppWeb.PageView, "page.html", assigns)
end
end
Embedding LiveView in Templates
Render LiveView components within static templates:
<h1>Temperature Control</h1>
<%= live_render(@conn, AppWeb.ThermostatLive) %>
Function Components in LiveView
Define and use function components in LiveView:
def weather_greeting(assigns) do
~H"""
<div title="My div" class={@class}>
<p>Hello <%= @name %></p>
<MyApp.Weather.city name="Kraków"/>
</div>
"""
end
Testing Views
Testing View Rendering
Test views directly using render_to_string/4:
defmodule HelloWeb.ErrorHTMLTest do
use HelloWeb.ConnCase, async: true
import Phoenix.Template
test "renders 404.html" do
assert render_to_string(HelloWeb.ErrorHTML, "404", "html", []) == "Not Found"
end
test "renders 500.html" do
assert render_to_string(HelloWeb.ErrorHTML, "500", "html", []) == "Internal Server Error"
end
end
Testing Function Components
Test components in isolation:
import Phoenix.LiveViewTest
test "renders greet component" do
assigns = %{messenger: "Phoenix"}
html = rendered_to_string(~H"""
<HelloWeb.HelloHTML.greet messenger={@messenger} />
""")
assert html =~ "Hello World, from Phoenix!"
end
When to Use This Skill
Use this skill when you need to:
- Create dynamic HTML templates for Phoenix applications
- Build reusable function components for consistent UI elements
- Implement conditional rendering based on application state
- Render lists and tables with dynamic data
- Create complex layouts with nested components and slots
- Integrate LiveView components with static templates
- Test view rendering logic and component behavior
- Build accessible and semantic HTML structures
- Implement responsive designs with dynamic classes
- Create forms with validation feedback
- Display flash messages and user notifications
- Render navigation menus and breadcrumbs
- Build card-based layouts and dashboards
- Implement pagination controls
Best Practices
- Use function components - Encapsulate reusable UI patterns in components
-
Declare attributes explicitly - Use
attrmacro for all component attributes - Provide default values - Make components flexible with sensible defaults
- Use semantic HTML - Choose appropriate HTML elements for accessibility
- Leverage slots - Use slots for flexible component composition
- Keep templates simple - Move complex logic to controller or context
-
Use :for shorthand - Prefer
:forattribute for simple iterations - Avoid inline styles - Use CSS classes for styling
- Test components - Write tests for complex component logic
- Document components - Add docstrings to explain component usage
-
Use verified routes - Always use
~psigil in templates - Escape user content - Let HEEx handle escaping automatically
- Optimize renders - Minimize computation in template code
- Use descriptive names - Name components and attributes clearly
- Follow conventions - Stick to Phoenix naming patterns
Common Pitfalls
- Putting logic in templates - Complex business logic belongs in contexts, not views
-
Not escaping HTML - Using
raw/1without sanitizing user input - Deeply nested templates - Creating hard-to-maintain template hierarchies
-
Missing attribute declarations - Not using
attrmacro for component attributes - Overusing inline conditionals - Making templates hard to read
- Not using components - Repeating markup instead of extracting components
- Forgetting slot checks - Not checking if optional slots are provided
- Mixing concerns - Combining data fetching with presentation logic
- Large template files - Creating monolithic templates instead of components
- Inconsistent formatting - Not following HEEx formatting conventions
- Using EEx instead of HEEx - Missing compile-time validation benefits
- Ignoring accessibility - Not adding ARIA labels and semantic markup
- Hardcoding values - Not using assigns for configurable content
- Not testing edge cases - Missing nil checks and empty state handling
- Excessive nesting - Creating deeply nested component trees
Resources
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-02-05
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!