View Source Phoenix.Component (Phoenix LiveView v0.18.13)

Define reusable function components with HEEx templates.

A function component is any function that receives an assigns map as an argument and returns a rendered struct built with the ~H sigil:

defmodule MyComponent do
  use Phoenix.Component

  def greet(assigns) do
    ~H"""
    <p>Hello, <%= @name %>!</p>
    """
  end
end

When invoked within a ~H sigil or HEEx template file:

<MyComponent.greet name="Jane" />

The following HTML is rendered:

<p>Hello, Jane!</p>

If the function component is defined locally, or its module is imported, then the caller can invoke the function directly without specifying the module:

<.greet name="Jane" />

For dynamic values, you can interpolate Elixir expressions into a function component:

<.greet name={@user.name} />

Function components can also accept blocks of HEEx content (more on this later):

<.card>
  <p>This is the body of my card!</p>
</.card>

Note how the name attribute automatically becomes the @name assign inside function components. This can be further leveraged by using two higher-level abstractions for us: attributes and slots.

attributes

Attributes

Phoenix.Component provides the attr/3 macro to declare what attributes the proceeding function component expects to receive when invoked:

attr :name, :string, required: true

def greet(assigns) do
  ~H"""
  <p>Hello, <%= @name %>!</p>
  """
end

By calling attr/3, it is now clear that greet/1 requires a string attribute called name present in its assigns map to properly render. Failing to do so will result in a compilation warning:

<MyComponent.greet />
  <!-- warning: missing required attribute "name" for component MyAppWeb.MyComponent.greet/1
           lib/app_web/my_component.ex:15 -->

Attributes can provide default values that are automatically merged into the assigns map:

attr :name, :string, default: "Bob"

Now you can invoke the function component without providing a value for name:

<.greet />

Rendering the following HTML:

<p>Hello, Bob!</p>

Accessing an attribute which is required and does not have a default value will fail. You must explicitly declare default: nil or assign a value programmatically with the assign_new/3 function.

Multiple attributes can be declared for the same function component:

attr :name, :string, required: true
attr :age, :integer, required: true

def celebrate(assigns) do
  ~H"""
  <p>
    Happy birthday <%= @name %>!
    You are <%= @age %> years old.
  </p>
  """
end

Allowing the caller to pass multiple values:

<.celebrate name={"Genevieve"} age={34} />

Rendering the following HTML:

<p>
  Happy birthday Genevieve!
  You are 34 years old.
</p>

Multiple function components can be defined in the same module, with different attributes. In the following example, <Components.greet/> requires a name, but does not require a title, and <Component.heading> requires a title, but does not require a name.

defmodule Components do
  use Phoenix.Component

  attr :title, :string, required: true

  def heading(assigns) do
    ~H"""
    <h1><%= @title %></h1>
    """
  end

  attr :name, :string, required: true

  def greet(assigns) do
    ~H"""
    <p>Hello <%= @name %></p>
    """
  end
end

With the attr/3 macro you have the core ingredients to create reusable function components. But what if you need your function components to support dynamic attributes, such as common HTML attributes to mix into a component's container?

global-attributes

Global Attributes

Global attributes are a set of attributes that a function component can accept when it declares an attribute of type :global. By default, the set of attributes accepted are those attributes common to all standard HTML tags. See Global attributes for a complete list of attributes.

Once a global attribute is declared, any number of attributes in the set can be passed by the caller without having to modify the function component itself.

Below is an example of a function component that accepts a dynamic number of global attributes:

attr :message, :string, required: true
attr :rest, :global

def notification(assigns) do
  ~H"""
  <span {@rest}><%= @message %></span>
  """
end

The caller can pass multiple global attributes (such as phx-* bindings or the class attribute):

<.notification message="You've got mail!" class="bg-green-200" phx-click="close" />

Rendering the following HTML:

<span class="bg-green-200" phx-click="close">You've got mail!</span>

Note that the function component did not have to explicitly declare a class or phx-click attribute in order to render.

Global attributes can define defaults which are merged with attributes provided by the caller. For example, you may declare a default class if the caller does not provide one:

attr :rest, :global, default: %{class: "bg-blue-200"}

Now you can call the function component without a class attribute:

<.notification message="You've got mail!" phx-click="close" />

Rendering the following HTML:

<span class="bg-blue-200" phx-click="close">You've got mail!</span>

Note that the global attribute cannot be provided directly and doing so will emit a warning. In other words, this is invalid:

<.notification message="You've got mail!" rest={%{"phx-click" => "close"}} />

included-globals

Included globals

You may also specify which attributes are included in addition to the known globals with the :include option. For example to support the form attribute on a button component:

# <.button form="my-form"/>
attr :rest, :global, include: ~w(form)
slot :inner_block
def button(assigns) do
  ~H"""
  <button {@rest}><%= render_slot(@inner_block) %></button>
  """
end

The :include option is useful to apply global additions on a case-by-case basis, but sometimes you want attributes to be available to all globals you provide, such as when using frameworks that use attribute prefixes, like Alpine.js's x-on:click. For these cases, custom global attribute prefixes can be provided, which we'll outline next.

custom-global-attribute-prefixes

Custom Global Attribute Prefixes

You can extend the set of global attributes by providing a list of attribute prefixes to use Phoenix.Component. Like the default attributes common to all HTML elements, any number of attributes that start with a global prefix will be accepted by function components defined in this module. By default, the following prefixes are supported: phx-, aria-, and data-. For example, to support the x- prefix used by Alpine.js, you can pass the :global_prefixes option to use Phoenix.Component:

use Phoenix.Component, global_prefixes: ~w(x-)

Now all function components defined in this module will accept any number of attributes prefixed with x-, in addition to the default global prefixes.

You can learn more about attributes by reading the documentation for attr/3.

slots

Slots

In addition to attributes, function components can accept blocks of HEEx content, referred to as slots. Slots enable further customization of the rendered HTML, as the caller can pass the function component HEEx content they want the component to render. Phoenix.Component provides the slot/3 macro used to declare slots for function components:

slot :inner_block, required: true

def button(assigns) do
  ~H"""
  <button>
    <%= render_slot(@inner_block) %>
  </button>
  """
end

The expression render_slot(@inner_block) renders the HEEx content. You can invoke this function component like so:

<.button>
  This renders <strong>inside</strong> the button!
</.button>

Which renders the following HTML:

<button>
  This renders <strong>inside</strong> the button!
</button>

Like the attr/3 macro, using the slot/3 macro will provide compile-time validations. For example, invoking button/1 without a slot of HEEx content will result in a compilation warning being emitted:

<.button />
  <!-- warning: missing required slot "inner_block" for component MyAppWeb.MyComponent.button/1
           lib/app_web/my_component.ex:15 -->

the-default-slot

The default slot

The example above uses the default slot, accessible as an assign named @inner_block, to render HEEx content via the render_slot/2 function.

If the values rendered in the slot need to be dynamic, you can pass a second value back to the HEEx content by calling render_slot/2:

slot :inner_block, required: true

attr :entries, :list, default: []

def unordered_list(assigns) do
  ~H"""
  <ul>
    <%= for entry <- @entries do %>
      <li><%= render_slot(@inner_block, entry) %></li>
    <% end %>
  </ul>
  """
end

When invoking the function component, you can use the special attribute :let to take the value that the function component passes back and bind it to a variable:

<.unordered_list :let={fruit} entries={~w(apples bananas cherries)}>
  I like <%= fruit %>!
</.unordered_list>

Rendering the following HTML:

<ul>
  <li>I like apples!</li>
  <li>I like bananas!</li>
  <li>I like cherries!</li>
</ul>

Now the separation of concerns is maintained: the caller can specify multiple values in a list attribute without having to specify the HEEx content that surrounds and separates them.

named-slots

Named slots

In addition to the default slot, function components can accept multiple, named slots of HEEx content. For example, imagine you want to create a modal that has a header, body, and footer:

slot :header
slot :inner_block, required: true
slot :footer, required: true

def modal(assigns) do
  ~H"""
  <div class="modal">
    <div class="modal-header">
      <%= render_slot(@header) || "Modal" %>
    </div>
    <div class="modal-body">
      <%= render_slot(@inner_block) %>
    </div>
    <div class="modal-footer">
      <%= render_slot(@footer) %>
    </div>
  </div>
  """
end

You can invoke this function component using the named slot HEEx syntax:

<.modal>
  This is the body, everything not in a named slot is rendered in the default slot.
  <:footer>
    This is the bottom of the modal.
  </:footer>
</.modal>

Rendering the following HTML:

<div class="modal">
  <div class="modal-header">
    Modal.
  </div>
  <div class="modal-body">
    This is the body, everything not in a named slot is rendered in the default slot.
  </div>
  <div class="modal-footer">
    This is the bottom of the modal.
  </div>
</div>

As shown in the example above, render_slot/1 returns nil when an optional slot is declared and none is given. This can be used to attach default behaviour.

slot-attributes

Slot attributes

Unlike the default slot, it is possible to pass a named slot multiple pieces of HEEx content. Named slots can also accept attributes, defined by passing a block to the slot/3 macro. If multiple pieces of content are passed, render_slot/2 will merge and render all the values.

Below is a table component illustrating multiple named slots with attributes:

slot :column, doc: "Columns with column labels" do
  attr :label, :string, required: true, doc: "Column label"
end

attr :rows, :list, default: []

def table(assigns) do
  ~H"""
  <table>
    <tr>
      <%= for col <- @column do %>
        <th><%= col.label %></th>
      <% end %>
    </tr>
    <%= for row <- @rows do %>
      <tr>
        <%= for col <- @column do %>
          <td><%= render_slot(col, row) %></td>
        <% end %>
      </tr>
    <% end %>
  </table>
  """
end

You can invoke this function component like so:

<.table rows={[%{name: "Jane", age: "34"}, %{name: "Bob", age: "51"}]}>
  <:column :let={user} label="Name">
    <%= user.name %>
  </:column>
  <:column :let={user} label="Age">
    <%= user.age %>
  </:column>
</.table>

Rendering the following HTML:

<table>
  <tr>
    <th>Name</th>
    <th>Age</th>
  </tr>
  <tr>
    <td>Jane</td>
    <td>34</td>
  </tr>
  <tr>
    <td>Bob</td>
    <td>51</td>
  </tr>
</table>

You can learn more about slots and the slot/3 macro in its documentation.

embedding-external-template-files

Embedding external template files

The embed_templates/1 macro can be used to embed .html.heex files as function components. The directory path is based on the current module (__DIR__), and a wildcard pattern may be used to select all files within a directory tree. For example, imagine a directory listing:

 components.ex
 cards
    pricing_card.html.heex
    features_card.html.heex

Then you can embed the page templates in your components.ex module and call them like any other function component:

defmodule MyAppWeb.Components do
  use Phoenix.Component

  embed_templates "cards/*"

  def landing_hero(assigns) do
    ~H"""
    <.pricing_card />
    <.features_card />
    """
  end
end

See embed_templates/1 for more information, including declarative assigns support for embedded templates.

Link to this section Summary

Components

Generates a dynamically named HTML tag.

Wraps tab focus around a container for accessibility.

Renders a form.

Intersperses separator slot between an enumerable.

Generates a link for live and href navigation.

A function component for rendering Phoenix.LiveComponent within a parent LiveView.

Builds a file input tag for a LiveView upload.

Generates an image preview on the client for a selected file.

Renders a title with automatic prefix/suffix on @page_title updates.

Macros

Declares attributes for a HEEx function components.

Embeds external template files into the module as function components.

The ~H sigil for writing HEEx templates inside source files.

Declares a slot. See slot/3 for more information.

Declares a function component slot.

Functions

Adds key-value pairs to assigns.

Adds a key-value pair to socket_or_assigns.

Assigns the given key with value from fun into socket_or_assigns if one does not yet exist.

Filters the assigns as a list of keywords for use in dynamic tag attributes.

Checks if the given key changed in socket_or_assigns.

Renders nested form inputs for associations or embeds.

Returns the flash message from the LiveView flash assign.

Renders a LiveView within a template.

Renders a slot entry with the given optional argument.

Converts a given data structure to a Phoenix.HTML.Form according to Phoenix.HTML.FormData.

Updates an existing key with fun in the given socket_or_assigns.

Returns the entry errors for an upload.

Returns the entry errors for an upload.

Link to this section Components

Generates a dynamically named HTML tag.

Raises an ArgumentError if the tag name is found to be unsafe HTML.

attributes

Attributes

  • name (:string) (required) - The name of the tag, such as div. Global attributes are accepted.

slots

Slots

  • inner_block

examples

Examples

<.dynamic_tag name="input" type="text"/>
<input type="text"/>
<.dynamic_tag name="p">content</.dynamic_tag>
<p>content</p>

Wraps tab focus around a container for accessibility.

This is an essential accessibility feature for interfaces such as modals, dialogs, and menus.

attributes

Attributes

  • id (:string) (required) - The DOM identifier of the container tag. Global attributes are accepted.

slots

Slots

  • inner_block (required) - The content rendered inside of the container tag.

examples

Examples

Simply render your inner content within this component and focus will be wrapped around the container as the user tabs through the containers content:

<.focus_wrap id="my-modal" class="bg-white">
  <div id="modal-content">
    Are you sure?
    <button phx-click="cancel">Cancel</button>
    <button phx-click="confirm">OK</button>
  </div>
</.focus_wrap>

Renders a form.

This function receives a form struct, generally created with to_form/2, and generates the relevant form tags. It can be used either inside LiveView or outside.

attributes

Attributes

  • for (:any) (required) - An existing form or the form source data.

  • action (:string) - The action to submit the form on. This attribute must be given if you intend to submit the form to a URL without LiveView.

    Defaults to nil.

  • as (:atom) - The server side parameter in which all params for this form will be collected. For example, setting as: :user_params means the parameters will be nested "user_params" in your handle_event or conn.params.user_params for regular HTTP requests.

    Defaults to nil.

  • multipart (:boolean) - Sets enctype to multipart/form-data. Required when uploading files.

    Defaults to false.

  • method (:string) - The HTTP method. It is only used if an :action is given. If the method is not get nor post, an input tag with name _method is generated alongside the form tag.

  • csrf_token (:any) - A token to authenticate the validity of requests. One is automatically generated when an action is given and the method is not get. When set to false, no token is generated.

Global attributes are accepted.

slots

Slots

  • inner_block (required) - The content rendered inside of the form tag.

examples-inside-liveview

Examples: inside LiveView

Inside LiveViews, the :for attribute is generally a form struct created with the to_form/1 function. to_form/1 expects either a map or an Ecto.Changeset as the source of data.

For example, you may use the parameters received in a Phoenix.LiveView.handle_event/3 callback to create an Ecto changeset and then use to_form/1 to convert it to a form. Then, in your templates, you pass the @form as argument to :for:

<.form
  for={@form}
  phx-change="change_name"
>
  <.input field={@form[:email]} />
</.form>

The .input component is generally defined as part of your own application and adds all styling necessary:

def input(assigns) do
  ~H"""
  <input type="text" name={@field.name} id={@field.id} value={@field.value} class="..." />
  """
end

A form accepts multiple options. For example, if you are doing file uploads and you want to capture submissions, you might write instead:

<.form
  for={@form}
  multipart
  phx-change="change_user"
  phx-submit="save_user"
>
  ...
  <input type="submit" value="Save" />
</.form>

Notice how both examples use phx-change. The LiveView must implement the phx-change event and store the input values as they arrive on change. This is important because, if an unrelated change happens on the page, LiveView should re-render the inputs with their updated values. Without phx-change, the inputs would otherwise be cleared. Alternatively, you can use phx-update="ignore" on the form to discard any updates.

using-the-for-attribute

Using the for attribute

The for attribute can also be a map or an Ecto.Changeset. In such cases, a form will be created on the fly, and you can capture it using :let:

<.form
  :let={form}
  for={@changeset}
  phx-change="change_user"
>

However, such approach is discouraged in LiveView for two reasons:

  • LiveView can better optimize your code if you access the form fields using @form[:field] rather than through the let-variable form

  • Ecto changesets are meant to be single use. By never storing the changeset in the assign, you will be less tempted to use it across operations

a-note-on-errors

A note on :errors

Even if changeset.errors is non-empty, errors will not be displayed in a form if the changeset :action is nil or :ignore.

This is useful for things like validation hints on form fields, e.g. an empty changeset for a new form. That changeset isn't valid, but we don't want to show errors until an actual user action has been performed.

For example, if the user submits and a Repo.insert/1 is called and fails on changeset validation, the action will be set to :insert to show that an insert was attempted, and the presence of that action will cause errors to be displayed. The same is true for Repo.update/delete.

If you want to show errors manually you can also set the action yourself, either directly on the Ecto.Changeset struct field or by using Ecto.Changeset.apply_action/2. Since the action can be arbitrary, you can set it to :validate or anything else to avoid giving the impression that a database operation has actually been attempted.

example-outside-liveview-regular-http-requests

Example: outside LiveView (regular HTTP requests)

The form component can still be used to submit forms outside of LiveView. In such cases, the action attribute MUST be given. Without said attribute, the form method and csrf token are discarded.

<.form :let={f} for={@changeset} action={Routes.comment_path(:create, @comment)}>
  <.input field={f[:body]} />
</.form>

In the example above, we use passed a changeset to for and captured the value using :let={f}. This approach is ok outside of LiveViews, as there are no change tracking optimizations to consider.

csrf-protection

CSRF protection

CSRF protection is a mechanism to ensure that the user who rendered the form is the one actually submitting it. This module generates a CSRF token by default. Your application should check this token on the server to avoid attackers from making requests on your server on behalf of other users. Phoenix by default checks this token.

When posting a form with a host in its address, such as "//host.com/path" instead of only "/path", Phoenix will include the host signature in the token and validate the token only if the accessed host is the same as the host in the token. This is to avoid tokens from leaking to third party applications. If this behaviour is problematic, you can generate a non-host specific token with Plug.CSRFProtection.get_csrf_token/0 and pass it to the form generator via the :csrf_token option.

Intersperses separator slot between an enumerable.

Useful when you need to add a separator between items such as when rendering breadcrumbs for navigation. Provides each item to the inner block.

examples

Examples

<.intersperse :let={item} enum={["home", "profile", "settings"]}>
  <:separator>
    <span class="sep">|</span>
  </:separator>
  <%= item %>
</.intersperse>

Renders the following markup:

home <span class="sep">|</span> profile <span class="sep">|</span> settings

attributes

Attributes

  • enum (:any) (required) - the enumerable to intersperse with separators.

slots

Slots

  • inner_block (required) - the inner_block to render for each item.
  • separator (required) - the slot for the separator.

Generates a link for live and href navigation.

attributes

Attributes

  • navigate (:string) - Navigates from a LiveView to a new LiveView. The browser page is kept, but a new LiveView process is mounted and its content on the page is reloaded. It is only possible to navigate between LiveViews declared under the same router Phoenix.LiveView.Router.live_session/3. Otherwise, a full browser redirect is used.

  • patch (:string) - Patches the current LiveView. The handle_params callback of the current LiveView will be invoked and the minimum content will be sent over the wire, as any other LiveView diff.

  • href (:any) - Uses traditional browser navigation to the new location. This means the whole page is reloaded on the browser.

  • replace (:boolean) - When using :patch or :navigate, should the browser's history be replaced with pushState?

    Defaults to false.

  • method (:string) - The HTTP method to use with the link. This is intended for usage outside of LiveView and therefore only works with the href={...} attribute. It has no effect on patch and navigate instructions.

    In case the method is not get, the link is generated inside the form which sets the proper information. In order to submit the form, JavaScript must be enabled in the browser.

    Defaults to "get".

  • csrf_token (:any) - A boolean or custom token to use for links with an HTTP method other than get. Defaults to true. Global attributes are accepted.

slots

Slots

  • inner_block (required) - The content rendered inside of the a tag.

examples

Examples

<.link href="/">Regular anchor link</.link>
<.link navigate={Routes.page_path(@socket, :index)} class="underline">home</.link>
<.link navigate={Routes.live_path(@socket, MyLive, dir: :asc)} replace={false}>
  Sort By Price
</.link>
<.link patch={Routes.page_path(@socket, :index, :details)}>view details</.link>
<.link href={URI.parse("https://elixir-lang.org")}>hello</.link>
<.link href="/the_world" method="delete" data-confirm="Really?">delete</.link>

javascript-dependency

JavaScript dependency

In order to support links where :method is not "get" or use the above data attributes, Phoenix.HTML relies on JavaScript. You can load priv/static/phoenix_html.js into your build tool.

data-attributes

Data attributes

Data attributes are added as a keyword list passed to the data key. The following data attributes are supported:

  • data-confirm - shows a confirmation prompt before generating and submitting the form when :method is not "get".

overriding-the-default-confirm-behaviour

Overriding the default confirm behaviour

phoenix_html.js does trigger a custom event phoenix.link.click on the clicked DOM element when a click happened. This allows you to intercept the event on its way bubbling up to window and do your own custom logic to enhance or replace how the data-confirm attribute is handled. You could for example replace the browsers confirm() behavior with a custom javascript implementation:

// listen on document.body, so it's executed before the default of
// phoenix_html, which is listening on the window object
document.body.addEventListener('phoenix.link.click', function (e) {
  // Prevent default implementation
  e.stopPropagation();
  // Introduce alternative implementation
  var message = e.target.getAttribute("data-confirm");
  if(!message){ return true; }
  vex.dialog.confirm({
    message: message,
    callback: function (value) {
      if (value == false) { e.preventDefault(); }
    }
  })
}, false);

Or you could attach your own custom behavior.

window.addEventListener('phoenix.link.click', function (e) {
  // Introduce custom behaviour
  var message = e.target.getAttribute("data-prompt");
  var answer = e.target.getAttribute("data-prompt-answer");
  if(message && answer && (answer != window.prompt(message))) {
    e.preventDefault();
  }
}, false);

The latter could also be bound to any click event, but this way you can be sure your custom code is only executed when the code of phoenix_html.js is run.

csrf-protection

CSRF Protection

By default, CSRF tokens are generated through Plug.CSRFProtection.

A function component for rendering Phoenix.LiveComponent within a parent LiveView.

While LiveViews can be nested, each LiveView starts its own process. A LiveComponent provides similar functionality to LiveView, except they run in the same process as the LiveView, with its own encapsulated state. That's why they are called stateful components.

attributes

Attributes

  • id (:string) (required) - A unique identifier for the LiveComponent. Note the id won't necessarily be used as the DOM id. That is up to the component to decide.

  • module (:atom) (required) - The LiveComponent module to render.

Any additional attributes provided will be passed to the LiveComponent as a map of assigns. See Phoenix.LiveComponent for more information.

examples

Examples

<.live_component module={MyApp.WeatherComponent} id="thermostat" city="Kraków" />
Link to this function

live_file_input(assigns)

View Source

Builds a file input tag for a LiveView upload.

attributes

Attributes

  • :upload - The %Phoenix.LiveView.UploadConfig{} struct.

Arbitrary attributes may be passed to be applied to the file input tag.

drag-and-drop

Drag and Drop

Drag and drop is supported by annotating the droppable container with a phx-drop-target attribute pointing to the DOM id of the file input. By default, the file input id is the upload ref, so the following markup is all that is required for drag and drop support:

<div class="container" phx-drop-target={@uploads.avatar.ref}>
  <!-- ... -->
  <.live_file_input upload={@uploads.avatar} />
</div>

examples

Examples

<.live_file_input upload={@uploads.avatar} />
Link to this function

live_img_preview(assigns)

View Source

Generates an image preview on the client for a selected file.

examples

Examples

<%= for entry <- @uploads.avatar.entries do %>
  <.live_img_preview entry={entry} width="75" />
<% end %>

Renders a title with automatic prefix/suffix on @page_title updates.

attributes

Attributes

  • prefix (:string) - A prefix added before the content of inner_block. Defaults to nil.
  • suffix (:string) - A suffix added after the content of inner_block. Defaults to nil.

slots

Slots

  • inner_block (required) - Content rendered inside the title tag.

examples

Examples

<.live_title prefix="MyApp  ">
  <%= assigns[:page_title] || "Welcome" %>
</.live_title>
<.live_title suffix="- MyApp">
  <%= assigns[:page_title] || "Welcome" %>
</.live_title>

Link to this section Macros

Link to this macro

attr(name, type, opts \\ [])

View Source (macro)

Declares attributes for a HEEx function components.

arguments

Arguments

  • name - an atom defining the name of the attribute. Note that attributes cannot define the same name as any other attributes or slots declared for the same component.

  • type - an atom defining the type of the attribute.

  • opts - a keyword list of options. Defaults to [].

types

Types

An attribute is declared by its name, type, and options. The following types are supported:

NameDescription
:anyany term
:stringany binary string
:atomany atom (including true, false, and nil)
:booleanany boolean
:integerany integer
:floatany float
:listany list of any arbitrary types
:mapany map of any arbitrary types
:globalany common HTML attributes, plus those defined by :global_prefixes
A struct moduleany module that defines a struct with defstruct/1

options

Options

  • :required - marks an attribute as required. If a caller does not pass the given attribute, a compile warning is issued.

  • :default - the default value for the attribute if not provided. If this option is not set and the attribute is not given, accessing the attribute will fail unless a value is explicitly set with assign_new/3.

  • :examples - a non-exhaustive list of values accepted by the attribute, used for documentation purposes.

  • :values - an exhaustive list of values accepted by the attributes. If a caller passes a literal not contained in this list, a compile warning is issued.

  • :doc - documentation for the attribute.

compile-time-validations

Compile-Time Validations

LiveView performs some validation of attributes via the :phoenix_live_view compiler. When attributes are defined, LiveView will warn at compilation time on the caller if:

  • A required attribute of a component is missing.

  • An unknown attribute is given.

  • You specify a literal attribute (such as value="string" or value, but not value={expr}) and the type does not match. The following types currently support literal validation: :string, :atom, :boolean, :integer, :float, :map and :list.

  • You specify a literal attribute and it is not a member of the :values list.

LiveView does not perform any validation at runtime. This means the type information is mostly used for documentation and reflection purposes.

On the side of the LiveView component itself, defining attributes provides the following quality of life improvements:

  • The default value of all attributes will be added to the assigns map upfront.

  • Attribute documentation is generated for the component.

  • Required struct types are annotated and emit compilation warnings. For example, if you specify attr :user, User, required: true and then you write @user.non_valid_field in your template, a warning will be emitted.

  • Calls made to the component are tracked for reflection and validation purposes.

documentation-generation

Documentation Generation

Public function components that define attributes will have their attribute types and docs injected into the function's documentation, depending on the value of the @doc module attribute:

  • if @doc is a string, the attribute docs are injected into that string. The optional placeholder [INSERT LVATTRDOCS] can be used to specify where in the string the docs are injected. Otherwise, the docs are appended to the end of the @doc string.

  • if @doc is unspecified, the attribute docs are used as the default @doc string.

  • if @doc is false, the attribute docs are omitted entirely.

The injected attribute docs are formatted as a markdown list:

  • name (:type) (required) - attr docs. Defaults to :default.

By default, all attributes will have their types and docs injected into the function @doc string. To hide a specific attribute, you can set the value of :doc to false.

example

Example

attr :name, :string, required: true
attr :age, :integer, required: true

def celebrate(assigns) do
  ~H"""
  <p>
    Happy birthday <%= @name %>!
    You are <%= @age %> years old.
  </p>
  """
end
Link to this macro

embed_templates(pattern, opts \\ [])

View Source (macro)

Embeds external template files into the module as function components.

options

Options

  • :root - The root directory to embed files. Defaults to the current module's directory (__DIR__)
  • :suffix - The string value to append to embedded function names. By default, function names will be the name of the template file excluding the format and engine.

A wildcard pattern may be used to select all files within a directory tree. For example, imagine a directory listing:

 components.ex
 pages
    about_page.html.heex
    welcome_page.html.heex

Then to embed the page templates in your components.ex module:

defmodule MyAppWeb.Components do
  use Phoenix.Component

  embed_templates "pages/*"
end

Now, your module will have an about_page/1 and welcome_page/1 function component defined. Embedded templates also support declarative assigns via bodyless function definitions, for example:

defmodule MyAppWeb.Components do
  use Phoenix.Component

  embed_templates "pages/*"

  attr :name, :string, required: true
  def welcome_page(assigns)

  slot :header
  def about_page(assigns)
end

Multiple invocations of embed_templates is also supported, which can be useful if you have more than one template format. For example:

defmodule MyAppWeb.Emails do
  use Phoenix.Component

  embed_templates "emails/*.html", suffix: "_html"
  embed_templates "emails/*.text", suffix: "_text"
end

Note: this function is the same as Phoenix.Template.embed_templates/2. It is also provided here for convenience and documentation purposes. Therefore, if you want to embed templates for other formats, which are not related to Phoenix.Component, prefer to import Phoenix.Template, only: [embed_templates: 1] than this module.

Link to this macro

sigil_H(arg, list)

View Source (macro)

The ~H sigil for writing HEEx templates inside source files.

HEEx is a HTML-aware and component-friendly extension of Elixir Embedded language (EEx) that provides:

  • Built-in handling of HTML attributes

  • An HTML-like notation for injecting function components

  • Compile-time validation of the structure of the template

  • The ability to minimize the amount of data sent over the wire

  • Out-of-the-box code formatting via mix format

example

Example

~H"""
<div title="My div" class={@class}>
  <p>Hello <%= @name %></p>
  <MyApp.Weather.city name="Kraków"/>
</div>
"""

syntax

Syntax

HEEx is built on top of Embedded Elixir (EEx). In this section, we are going to cover the basic constructs in HEEx templates as well as its syntax extensions.

interpolation

Interpolation

Both HEEx and EEx templates use <%= ... %> for interpolating code inside the body of HTML tags:

<p>Hello, <%= @name %></p>

Similarly, conditionals and other block Elixir constructs are supported:

<%= if @show_greeting? do %>
  <p>Hello, <%= @name %></p>
<% end %>

Note we don't include the equal sign = in the closing <% end %> tag (because the closing tag does not output anything).

There is one important difference between HEEx and Elixir's builtin EEx. HEEx uses a specific annotation for interpolating HTML tags and attributes. Let's check it out.

heex-extension-defining-attributes

HEEx extension: Defining attributes

Since HEEx must parse and validate the HTML structure, code interpolation using <%= ... %> and <% ... %> are restricted to the body (inner content) of the HTML/component nodes and it cannot be applied within tags.

For instance, the following syntax is invalid:

<div class="<%= @class %>">
  ...
</div>

Instead do:

<div class={@class}>
  ...
</div>

You can put any Elixir expression between { ... }. For example, if you want to set classes, where some are static and others are dynamic, you can using string interpolation:

<div class={"btn btn-#{@type}"}>
  ...
</div>

The following attribute values have special meaning:

  • true - if a value is true, the attribute is rendered with no value at all. For example, <input required={true}> is the same as <input required>;

  • false or nil - if a value is false or nil, the attribute is not rendered;

  • list (only for the class attribute) - each element of the list is processed as a different class. nil and false elements are discarded.

For multiple dynamic attributes, you can use the same notation but without assigning the expression to any specific attribute.

<div {@dynamic_attrs}>
  ...
</div>

The expression inside {...} must be either a keyword list or a map containing the key-value pairs representing the dynamic attributes.

You can pair this notation assigns_to_attributes/2 to strip out any internal LiveView attributes and user-defined assigns from being expanded into the HTML tag:

<div {assigns_to_attributes(assigns, [:visible])}>
  ...
</div>

The above would add all caller attributes into the HTML, but strip out LiveView assigns like slots, as well as user-defined assigns like :visible that are not meant to be added to the HTML itself. This approach is useful to allow a component to accept arbitrary HTML attributes like class, ARIA attributes, etc.

heex-extension-defining-function-components

HEEx extension: Defining function components

Function components are stateless components implemented as pure functions with the help of the Phoenix.Component module. They can be either local (same module) or remote (external module).

HEEx allows invoking these function components directly in the template using an HTML-like notation. For example, a remote function:

<MyApp.Weather.city name="Kraków"/>

A local function can be invoked with a leading dot:

<.city name="Kraków"/>

where the component could be defined as follows:

defmodule MyApp.Weather do
  use Phoenix.Component

  def city(assigns) do
    ~H"""
    The chosen city is: <%= @name %>.
    """
  end

  def country(assigns) do
    ~H"""
    The chosen country is: <%= @name %>.
    """
  end
end

It is typically best to group related functions into a single module, as opposed to having many modules with a single render/1 function. Function components support other important features, such as slots. You can learn more about components in Phoenix.Component.

heex-extension-special-attributes

HEEx extension: special attributes

Apart from normal HTML attributes, HEEx also supports some special attributes such as :let and :for.

:let

This is used by components and slots that want to yield a value back to the caller. For an example, see how form/1 works:

<.form :let={f} for={@form} phx-change="validate" phx-submit="save">
  <.input field={f[:username]} type="text" />
  ...
</.form>

Notice how the variable f, defined by .form is used by your input component. The Phoenix.Component module has detailed documentation on how to use and implement such functionality.

:if and :for

It is a syntax sugar for <%= if .. do %> and <%= for .. do %> that can be used in regular HTML, function components, and slots.

For example in an HTML tag:

<table id="admin-table" :if={@admin?}>
  <tr :for={user <- @users}>
    <td><%= user.name %></td>
  </tr>
<table>

The snippet above will only render the table if @admin? is true, and generate a tr per user as you would expect from the collection.

:for can be used similarly in function components:

<.error :for={msg <- @errors} message={msg}/>

Which is equivalent to writing:

<%= for msg <- @errors do %>
  <.error message={msg} />
<% end %>

And :for in slots behaves the same way:

<.table id="my-table" rows={@users}>
  <:col :for={header <- @headers} :let={user}>
    <td><%= user[:header] %></td>
  </:col>
<table>

You can also combine :for and :if for tags, components, and slot to act as a filter:

<.error :for={msg <- @errors} :if={msg != nil} message={msg} />

code-formatting

Code formatting

You can automatically format HEEx templates (.heex) and ~H sigils using Phoenix.LiveView.HTMLFormatter. Please check that module for more information.

Link to this macro

slot(name, opts \\ [])

View Source (macro)

Declares a slot. See slot/3 for more information.

Link to this macro

slot(name, opts, block)

View Source (macro)

Declares a function component slot.

arguments

Arguments

  • name - an atom defining the name of the slot. Note that slots cannot define the same name as any other slots or attributes declared for the same component.

  • opts - a keyword list of options. Defaults to [].

  • block - a code block containing calls to attr/3. Defaults to nil.

options

Options

  • :required - marks a slot as required. If a caller does not pass a value for a required slot, a compilation warning is emitted. Otherwise, an omitted slot will default to [].

  • :doc - documentation for the slot. Any slot attributes declared will have their documentation listed alongside the slot.

slot-attributes

Slot Attributes

A named slot may declare attributes by passing a block with calls to attr/3.

Unlike attributes, slot attributes cannot accept the :default option. Passing one will result in a compile warning being issued.

the-default-slot

The Default Slot

The default slot can be declared by passing :inner_block as the name of the slot.

Note that the :inner_block slot declaration cannot accept a block. Passing one will result in a compilation error.

compile-time-validations

Compile-Time Validations

LiveView performs some validation of slots via the :phoenix_live_view compiler. When slots are defined, LiveView will warn at compilation time on the caller if:

  • A required slot of a component is missing.

  • An unknown slot is given.

  • An unknown slot attribute is given.

On the side of the function component itself, defining attributes provides the following quality of life improvements:

  • Slot documentation is generated for the component.

  • Calls made to the component are tracked for reflection and validation purposes.

documentation-generation

Documentation Generation

Public function components that define slots will have their docs injected into the function's documentation, depending on the value of the @doc module attribute:

  • if @doc is a string, the slot docs are injected into that string. The optional placeholder [INSERT LVATTRDOCS] can be used to specify where in the string the docs are injected. Otherwise, the docs are appended to the end of the @doc string.

  • if @doc is unspecified, the slot docs are used as the default @doc string.

  • if @doc is false, the slot docs are omitted entirely.

The injected slot docs are formatted as a markdown list:

  • name (required) - slot docs. Accepts attributes:
    • name (:type) (required) - attr docs. Defaults to :default.

By default, all slots will have their docs injected into the function @doc string. To hide a specific slot, you can set the value of :doc to false.

example

Example

slot :header
slot :inner_block, required: true
slot :footer

def modal(assigns) do
  ~H"""
  <div class="modal">
    <div class="modal-header">
      <%= render_slot(@header) || "Modal" %>
    </div>
    <div class="modal-body">
      <%= render_slot(@inner_block) %>
    </div>
    <div class="modal-footer">
      <%= render_slot(@footer) || submit_button() %>
    </div>
  </div>
  """
end

As shown in the example above, render_slot/1 returns nil when an optional slot is declared and none is given. This can be used to attach default behaviour.

Link to this section Functions

Link to this function

assign(socket_or_assigns, keyword_or_map)

View Source

Adds key-value pairs to assigns.

The first argument is either a LiveView socket or an assigns map from function components.

A keyword list or a map of assigns must be given as argument to be merged into existing assigns.

examples

Examples

iex> assign(socket, name: "Elixir", logo: "💧")
iex> assign(socket, %{name: "Elixir"})
Link to this function

assign(socket_or_assigns, key, value)

View Source

Adds a key-value pair to socket_or_assigns.

The first argument is either a LiveView socket or an assigns map from function components.

examples

Examples

iex> assign(socket, :name, "Elixir")
Link to this function

assign_new(socket_or_assigns, key, fun)

View Source

Assigns the given key with value from fun into socket_or_assigns if one does not yet exist.

The first argument is either a LiveView socket or an assigns map from function components.

This function is useful for lazily assigning values and sharing assigns. We will cover both use cases next.

lazy-assigns

Lazy assigns

Imagine you have a function component that accepts a color:

<.my_component color="red" />

The color is also optional, so you can skip it:

<.my_component />

In such cases, the implementation can use assign_new to lazily assign a color if none is given. Let's make it so it picks a random one when none is given:

def my_component(assigns) do
  assigns = assign_new(assigns, :bg_color, fn -> Enum.random(~w(bg-red-200 bg-green-200 bg-blue-200)) end)

  ~H"""
  <div class={@bg_color}>
    Example
  </div>
  """
end

sharing-assigns

Sharing assigns

It is possible to share assigns between the Plug pipeline and LiveView on disconnected render and between LiveViews when connected.

when-disconnected

When disconnected

When a user first accesses an application using LiveView, the LiveView is first rendered in its disconnected state, as part of a regular HTML response. By using assign_new in the mount callback of your LiveView, you can instruct LiveView to re-use any assigns already set in conn during disconnected state.

Imagine you have a Plug that does:

# A plug
def authenticate(conn, _opts) do
  if user_id = get_session(conn, :user_id) do
    assign(conn, :current_user, Accounts.get_user!(user_id))
  else
    send_resp(conn, :forbidden)
  end
end

You can re-use the :current_user assign in your LiveView during the initial render:

def mount(_params, %{"user_id" => user_id}, socket) do
  {:ok, assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)}
end

In such case conn.assigns.current_user will be used if present. If there is no such :current_user assign or the LiveView was mounted as part of the live navigation, where no Plug pipelines are invoked, then the anonymous function is invoked to execute the query instead.

when-connected

When connected

LiveView is also able to share assigns via assign_new within nested LiveView. If the parent LiveView defines a :current_user assign and the child LiveView also uses assign_new/3 to fetch the :current_user in its mount/3 callback, as above, the assign will be fetched from the parent LiveView, once again avoiding additional database queries.

Note that fun also provides access to the previously assigned values:

assigns =
    assigns
    |> assign_new(:foo, fn -> "foo" end)
    |> assign_new(:bar, fn %{foo: foo} -> foo <> "bar" end)
Link to this function

assigns_to_attributes(assigns, exclude \\ [])

View Source

Filters the assigns as a list of keywords for use in dynamic tag attributes.

Useful for transforming caller assigns into dynamic attributes while stripping reserved keys from the result.

examples

Examples

Imagine the following my_link component which allows a caller to pass a new_window assign, along with any other attributes they would like to add to the element, such as class, data attributes, etc:

<.my_link to="/" id={@id} new_window={true} class="my-class">Home</.my_link>

We could support the dynamic attributes with the following component:

def my_link(assigns) do
  target = if assigns[:new_window], do: "_blank", else: false
  extra = assigns_to_attributes(assigns, [:new_window, :to])

  assigns =
    assigns
    |> assign(:target, target)
    |> assign(:extra, extra)

  ~H"""
  <a href={@to} target={@target} {@extra}>
    <%= render_slot(@inner_block) %>
  </a>
  """
end

The above would result in the following rendered HTML:

<a href="/" target="_blank" id="1" class="my-class">Home</a>

The second argument (optional) to assigns_to_attributes is a list of keys to exclude. It typically includes reserved keys by the component itself, which either do not belong in the markup, or are already handled explicitly by the component.

Link to this function

changed?(socket_or_assigns, key)

View Source

Checks if the given key changed in socket_or_assigns.

The first argument is either a LiveView socket or an assigns map from function components.

examples

Examples

iex> changed?(socket, :count)

Renders nested form inputs for associations or embeds.

attributes

Attributes

  • field (Phoenix.HTML.FormField) (required) - A %Phoenix.HTML.Form{}/field name tuple, for example: {@form[:email]}.

  • id (:string) - The id to be used in the form, defaults to the concatenation of the given field to the parent form id.

  • as (:atom) - The name to be used in the form, defaults to the concatenation of the given field to the parent form name.

  • default (:any) - The value to use if none is available.

  • prepend (:list) - The values to prepend when rendering. This only applies if the field value is a list and no parameters were sent through the form.

  • append (:list) - The values to append when rendering. This only applies if the field value is a list and no parameters were sent through the form.

  • skip_hidden (:boolean) - Skip the automatic rendering of hidden fields to allow for more tight control over the generated markup.

    Defaults to false.

slots

Slots

  • inner_block (required) - The content rendered for each nested form.

examples

Examples

<.form
  :let={f}
  phx-change="change_name"
>
  <.inputs_for :let={f_nested} field={f[:nested]}>
    <.input type="text" field={f_nested[:name]} />
  </.inputs_for>
</.form>
This function is deprecated. Use Phoenix.Flash.get/2 in Phoenix v1.7+.

Returns the flash message from the LiveView flash assign.

examples

Examples

<p class="alert alert-info"><%= live_flash(@flash, :info) %></p>
<p class="alert alert-danger"><%= live_flash(@flash, :error) %></p>
Link to this function

live_render(conn_or_socket, view, opts \\ [])

View Source

Renders a LiveView within a template.

This is useful in two situations:

  • When rendering a child LiveView inside a LiveView.

  • When rendering a LiveView inside a regular (non-live) controller/view.

options

Options

  • :session - a map of binary keys with extra session data to be serialized and sent to the client. All session data currently in the connection is automatically available in LiveViews. You can use this option to provide extra data. Remember all session data is serialized and sent to the client, so you should always keep the data in the session to a minimum. For example, instead of storing a User struct, you should store the "user_id" and load the User when the LiveView mounts.

  • :container - an optional tuple for the HTML tag and DOM attributes to be used for the LiveView container. For example: {:li, style: "color: blue;"}. By default it uses the module definition container. See the "Containers" section below for more information.

  • :id - both the DOM ID and the ID to uniquely identify a LiveView. An :id is automatically generated when rendering root LiveViews but it is a required option when rendering a child LiveView.

  • :sticky - an optional flag to maintain the LiveView across live redirects, even if it is nested within another LiveView. If you are rendering the sticky view within your live layout, make sure that the sticky view itself does not use the same layout. You can do so by returning {:ok, socket, layout: false} from mount.

examples

Examples

When rendering from a controller/view, you can call:

<%= live_render(@conn, MyApp.ThermostatLive) %>

Or:

<%= live_render(@conn, MyApp.ThermostatLive, session: %{"home_id" => @home.id}) %>

Within another LiveView, you must pass the :id option:

<%= live_render(@socket, MyApp.ThermostatLive, id: "thermostat") %>

containers

Containers

When a LiveView is rendered, its contents are wrapped in a container. By default, the container is a div tag with a handful of LiveView specific attributes.

The container can be customized in different ways:

  • You can change the default container on use Phoenix.LiveView:

    use Phoenix.LiveView, container: {:tr, id: "foo-bar"}
  • You can override the container tag and pass extra attributes when calling live_render (as well as on your live call in your router):

    live_render socket, MyLiveView, container: {:tr, class: "highlight"}

If you don't want the container to affect layout, you can use the CSS property display: contents or a class that applies it, like Tailwind's .contents.

Link to this macro

render_slot(slot, argument \\ nil)

View Source (macro)

Renders a slot entry with the given optional argument.

<%= render_slot(@inner_block, @form) %>

If the slot has no entries, nil is returned.

If multiple slot entries are defined for the same slot,render_slot/2 will automatically render all entries, merging their contents. In case you want to use the entries' attributes, you need to iterate over the list to access each slot individually.

For example, imagine a table component:

<.table rows={@users}>
  <:col :let={user} label="Name">
    <%= user.name %>
  </:col>

  <:col :let={user} label="Address">
    <%= user.address %>
  </:col>
</.table>

At the top level, we pass the rows as an assign and we define a :col slot for each column we want in the table. Each column also has a label, which we are going to use in the table header.

Inside the component, you can render the table with headers, rows, and columns:

def table(assigns) do
  ~H"""
  <table>
    <tr>
      <%= for col <- @col do %>
        <th><%= col.label %></th>
      <% end %>
    </tr>
    <%= for row <- @rows do %>
      <tr>
        <%= for col <- @col do %>
          <td><%= render_slot(col, row) %></td>
        <% end %>
      </tr>
    <% end %>
  </table>
  """
end
Link to this function

to_form(data, options \\ [])

View Source

Converts a given data structure to a Phoenix.HTML.Form according to Phoenix.HTML.FormData.

This is commonly used to convert a map or an Ecto changeset into a form to be given to the form/1 component. For example, if you want to create a form based on handle_event parameters, you could do:

def handle_event("submitted", params, socket) do
  {:noreply, assign(socket, form: to_form(params))}
end

However, most typically, we specify a name to nest the parameters:

def handle_event("submitted", %{"user" => user_params}, socket) do
  {:noreply, assign(socket, form: to_form(user_params, as: :user))}
end

When using changesets, the name :as is automatically retrieved from the schema. For example, if you have a user schema:

defmodule MyApp.Users.User do
  use Ecto.Schema

  schema "..." do
    ...
  end
end

And then you create a changeset which you pass to to_form:

%MyApp.Blog.Post{}
|> Ecto.Changeset.change()
|> to_form()

Phoenix will take care of getting all of the relevant information from the changeset, including errors, data, and names for you. In this case, the parameters will be available under %{"post" => post_params}.

If an existing Phoenix.HTML.Form struct is given, the options below will override its existing values if given. Then the remaining options are merged with the existing form options.

options

Options

  • :as - the name prefix to be used in form inputs
  • :id - the id prefix to be used in form inputs

The underlying data may accept additional options when converted to forms. For example, a map accepts :errors to list errors, but such option is not accepted by changesets.

Link to this function

update(socket_or_assigns, key, fun)

View Source

Updates an existing key with fun in the given socket_or_assigns.

The first argument is either a LiveView socket or an assigns map from function components.

The update function receives the current key's value and returns the updated value. Raises if the key does not exist.

The update function may also be of arity 2, in which case it receives the current key's value as the first argument and the current assigns as the second argument. Raises if the key does not exist.

examples

Examples

iex> update(socket, :count, fn count -> count + 1 end)
iex> update(socket, :count, &(&1 + 1))
iex> update(socket, :max_users_this_session, fn current_max, %{users: users} ->
       max(current_max, length(users))
     end)

Returns the entry errors for an upload.

The following error may be returned:

  • :too_many_files - The number of selected files exceeds the :max_entries constraint

examples

Examples

def error_to_string(:too_many_files), do: "You have selected too many files"
<%= for err <- upload_errors(@uploads.avatar) do %>
  <div class="alert alert-danger">
    <%= error_to_string(err) %>
  </div>
<% end %>
Link to this function

upload_errors(conf, entry)

View Source

Returns the entry errors for an upload.

The following errors may be returned:

  • :too_large - The entry exceeds the :max_file_size constraint
  • :not_accepted - The entry does not match the :accept MIME types

examples

Examples

def error_to_string(:too_large), do: "Too large"
def error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
<%= for entry <- @uploads.avatar.entries do %>
  <%= for err <- upload_errors(@uploads.avatar, entry) do %>
    <div class="alert alert-danger">
      <%= error_to_string(err) %>
    </div>
  <% end %>
<% end %>