phoenix-views-templates

Phoenix Views and Templates

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "phoenix-views-templates" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-phoenix-views-templates

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 attr macro 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 :for attribute 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 ~p sigil 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/1 without sanitizing user input

  • Deeply nested templates - Creating hard-to-maintain template hierarchies

  • Missing attribute declarations - Not using attr macro 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

  • Phoenix Components Guide

  • Phoenix.Component Documentation

  • HEEx Template Engine

  • Phoenix Templates Guide

  • Phoenix Views Documentation

  • Phoenix LiveView Documentation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

storybook-story-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

atomic-design-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review