| Syntax | Meaning |
|---|---|
{@variable} | Output an assign value (HTML-escaped) |
{expression} | Output any Elixir expression (HTML-escaped) |
{"string #{interpolation}"} | Output interpolated string |
<%= if @condition do %>
...shown when true...
<% else %>
...shown when false...
<% end %>
<%= for item <- @list do %>
...repeated for each item...
<% end %>
<%= case @value do %>
<% :a -> %> ...
<% :b -> %> ...
<% end %>
<%= cond do %>
<% @count > 10 -> %> ...
<% @count > 0 -> %> ...
<% true -> %> ...default...
<% end %>
<%= %> outputs the result. <% %> (no =) executes but doesn't output. <%!-- --%> is an HTML comment that is stripped from output.
| Attribute | Triggers | Handler |
|---|---|---|
phx-click="name" | Click | handle_event("name", params, socket) |
phx-change="name" | Form field change | handle_event("name", params, socket) |
phx-submit="name" | Form submit | handle_event("name", params, socket) |
phx-value-key={val} | n/a | Adds %{"key" => val} to params |
phx-hook="Name" | n/a | Attaches a JavaScript hook |
data-confirm="msg" | n/a | Shows browser confirm dialog before event |
Values from phx-value-* are always strings. Use String.to_integer/1 in the handler if you need an integer.
| Attribute | Behavior |
|---|---|
navigate={~p"/path"} | Full LiveView navigation (new mount) |
patch={~p"/path"} | Same LiveView, new URL (calls handle_params) |
href={~p"/path"} | Traditional full-page navigation |
<!-- Function component (defined in CoreComponents or same module) -->
<.button>Click me</.button>
<.input field={@form[:title]} type="text" />
<.icon name="hero-trash" class="w-4 h-4" />
<!-- Module component -->
<Layouts.app flash={@flash}>
...content...
</Layouts.app>
<!-- Form component -->
<.form for={@form} phx-change="validate" phx-submit="save">
<.input field={@form[:email]} type="email" />
<button type="submit">Save</button>
</.form>
<!-- Link component -->
<.link navigate={~p"/dashboard"}>Dashboard</.link>
<.link href={~p"/users/log-out"} method="delete">Log out</.link>
.component (with dot) = function component. Module.component = component from another module. Both receive attributes as assigns.
<!-- Static attribute -->
class="text-sm"
<!-- Dynamic attribute -->
class={@dynamic_class}
<!-- Conditional class list -->
class={[
"base-class",
@active && "text-bold",
if(@selected, do: "bg-blue", else: "bg-gray")
]}
<!-- Dynamic ID -->
id={"item-#{@item.id}"}
~p sigil~p"/dashboard" # verified route (compile-time checked)
~p"/sessions/#{session.id}" # with interpolation
~p"/t/#{@talk.slug}" # using assigns
~p routes are checked at compile time. If the route doesn't exist in router.ex, you get a warning.