<!-- LLM_VERSION_INFO
FORMAT: text/markdown
CONTENT_TYPE: article
ORIGINAL_URL: https://fly.io/phoenix-files/function-components
ALTERNATE_VERSION: phoenix-files/function-components/index.html (text/html)
EXTRACTION_DATE: 2026-04-18T22:26:02.178Z

This is the markdown version with text-only content (images converted to alt-text).
For rich formatting with images, request the HTML version at: phoenix-files/function-components/index.html
-->

**Author:** Berenice Medel

**Author:** Chris Nicoll

## The problem

We’d like a way to reuse code for UI components that are very similar in structure, but carry different content.

Imagine we’re writing a [Phoenix](https://www.phoenixframework.org/) [LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html) app that frequently uses modals to display or save information. For a consistent experience, they might all have HTML elements for header, body, and footer regions. We’d prefer not to repeat this markup for all possible variations!

## The solution

This is a job for LiveView’s [function components](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) (`Phoenix.Component`).

A function component is basically a wrapper for a [`~H` sigil](https://hexdocs.pm/phoenix_live_view/0.17.0/Phoenix.LiveView.Helpers.html#sigil_H/2) that provides a template for customized content. It doesn’t have state of its own.

The `~H` sigil lets us inject [HEEx](https://hexdocs.pm/phoenix_live_view/assigns-eex.html) templating code into our source, to be interpreted and rendered into our LiveView. It’s used not only in defining a template for a component, but also in rendering it.

When we call the function component, we pass our unique content to it, either through its `assigns` parameter, or, if we need to pass whole blocks of HTML, using the [slots](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-slots) mechanism. Or both.

## Example: multi-part modals

Let’s go back to our modal example. We’ll lay some groundwork with a basic function component we’ll call `modal`, and morph it to demonstrate the following powers:

- [displaying strings](/content/phoenix-files/function-components/#custom-text-using-assigns/index.html) from an assigns map we pass to it
- displaying custom HTML we insert in its [default slot](/content/phoenix-files/function-components/#custom-html-using-slots/index.html)  
- placing multiple blocks of custom HTML through [_named_ slots](/content/phoenix-files/function-components/#named-slots/index.html)  
- [dealing gracefully](/content/phoenix-files/function-components/#optional-slots/index.html) with slots that are not always defined
- passing values into the component through a [named slot’s _attributes_](/content/phoenix-files/function-components/#slot-attributes/index.html)  
- passing a value from the component’s assigns map [into one of its slots](/content/phoenix-files/function-components/#passing-assign-values-to-the-caller-s-slot/index.html)

### Custom text using assigns

We call `use Phoenix.Component` at the top of our module to import the functions defined in `Phoenix.LiveView` and `Phoenix.LiveView.Helpers`. Then we can go ahead and define our function component.

```elixir

defmodule MyAppWeb.UI do
  use Phoenix.Component

def modal(assigns) do
    ~H"""
      <div class="modal-container">
        <div class="header">
          <h1><%= @title %></h1>
        </div>

<div class="modal-body">
          <p class="text-sm text-gray-500">
            <%= @body %>
          </p>
        </div>

<div class="modal-footer">
          <button>Ok</button>
          <button>Cancel</button>
        </div>
      </div>
    """
  end
end
```

This defines a function, called `modal/1`, that renders an HTML div element with class `"modal-container"`, enclosing more divs for the three distinct parts of our modal: header, body, and footer.

Within the `"header"` div, `<%= @title %>` will substitute the value of the assign `@title` on render (we could equally well write it `<%= assigns.title %>`; it’s just longer). In the div with class `"modal-body"`, `<%= @body %>` will give us the value of `@body`. The component will expect us to supply it with a map of assigns that include these, when we call it.

The `modal-footer` div contains two `buttons`, `“Ok”` and `“Cancel,”` that for the moment don’t do anything.

Now we can call `.modal` inside a `sigil_H` to render it, providing the `@title` and `@body` assigns in the opening tag:

```elixir
alias MyAppWeb.UI
~H"""
<UI.modal
  title="My basic modal"
  body="My modal content">
</UI.modal>
"""
```

If we import the module into the view where we’ll render it, we don’t have to use the full module name:

```elixir
import MyAppWeb.UI
~H"""
<.modal title="My basic modal" body="My modal content"></.modal>
"""
```

With some CSS magic, we’ll get a modal like this:

### Custom HTML using slots

What if we want to put some custom HTML into the body of the modal? We can’t fit a block of HTML into an assign. That’s where slots come in.

When you include content inside a function component—that is, between its opening and closing tags—it gets assigned to the component’s _default slot,_`@inner_block`. This assign can be rendered in our component with a [`render_slot/2`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#render_slot/2) macro in the template like this:

```elixir
<%= render_slot(@inner_block) %>
```

You could even use other components within the slot. We’ll keep our demonstration simple: just a bit of custom HTML and some graphics.

Let’s imagine that we want to display a modal to confirm that the user settings have been saved successfully. We tell the `modal` function to render the `@inner_block` in the `"modal-body"` div:

```elixir
def modal(assigns) do
  ~H"""
  <div class="modal-container">
    <div class="header">
      <h1><%= @title %></h1>
    </div>

<div class="modal-body">
      <p class="text-sm text-gray-500">
        <%= render_slot(@inner_block) %>.   <!-- HERE  -->
      </p>
    </div>

<div class="modal-footer">
      <button>Ok</button>
      <button>Cancel</button>
    </div>
  </div>
  """
 end
```

And put our custom HTML content inside the call to `modal` like so:

```elixir
~H"""
<.modal title="My basic modal">

<!-- body content  -->
  <div class="text-center justify-center items-center">
    <h1 class="text-green-600">Great!</h1>
    <p>Your settings have been <strong>successfully</strong> saved</p>
    <div class="flex items-center justify-center">
      <img class="h-20 w-20 rounded-full flex items-center"
      src={Routes.static_path(@socket, "/images/check.webp")}
      alt="">
    </div>
  </div>
  <!-- body content  -->

</.modal>
"""
```

Our fancy custom message gets rendered in our modal.

### Named slots

We’ve modified the body of the modal with `@inner_block`, but we might want custom markup in the header and footer too. But wait—if `@inner_block` is the _default_ slot, that implies there can be others. It’s true! They need different names, so you can refer to them (a named slot can also take an argument, as we’ll see below).

Named slots are defined with the following syntax, when we call our function component:

```elixir
<:name_of_my_slot>
  My slot content
</:name_of_my_slot>
```

Let’s prep the template to customize the header and the buttons in the footer using slot assigns named `@header`, `@confirm`, and `@cancel`:

```elixir
def modal(assigns) do
  ~H"""
  <div class="modal-container">
    <div class="header">
      <%= render_slot(@header) %>                 <!-- HERE  -->
    </div>

<div class="modal-body">
      <p class="text-sm text-gray-500">
        <%= render_slot(@inner_block) %>          <!-- HERE  -->
      </p>
    </div>

<div class="modal-footer">                    <!-- HERE  -->
      <button><%= render_slot(@confirm) %></button>
      <button><%= render_slot(@cancel) %></button>
    </div>
  </div>
  """
 end
```

And here’s how we specify the contents of those slots for this variant of the modal, along with the default slot we already filled:

```elixir
~H"""
<.modal title="My basic modal">
  <!-- named slot: header  -->
  <:header>
    <div class="border-b-4 border-green-600">
      Success modal
    </div>
  </:header>

<!-- inner_block slot  -->
  <div class="text-center justify-center items-center">
    <h1 class="text-green-600">Great!</h1>
    <p>Your settings have been <strong>successfully</strong> saved</p>
    <div class="flex items-center justify-center">
      <img class="h-20 w-20 rounded-full flex items-center justify-center"
      src={Routes.static_path(@socket, "/images/check.webp")}
      alt="">
    </div>
  </div>

<!-- footer named slots  -->
  <:confirm>
    Return to profile
  </:confirm>

<:cancel>
    Back to index
  </:cancel>

</.modal>
"""
```

Note that everything in the body of our function component that is not inside a _named slot_ is inside the default slot `@inner_block`.

### Optional slots

Some modals might not need all the slots we’ve told `modal` to render. Our function component will choke on any undefined assigns, so we start our definition by making sure `@header`, `@confirm`, or `@cancel` exist:

```elixir
def modal(assigns) do
  assigns =
    assigns
    |> assign_new(:header, fn -> [] end)
    |> assign_new(:confirm, fn -> [] end)
    |> assign_new(:cancel, fn -> [] end)

~H"""
  <div class="modal-container">
    <div class="header">
      <%= render_slot(@header) %>
    </div>
    ...
```

Here, the `assign_new/2` function initializes any of those that’s missing a value with an empty list.

Call `modal` without any of the named slots:

```elixir
~H"""
<.modal title="My basic modal">

<!-- this modal has no footer or header  -->

<div class="text-center justify-center items-center">
    <h1 class="text-green-600">Great!</h1>
    <p>Your settings have been <strong>successfully</strong> saved</p>
    <div class="flex items-center justify-center">
      <img class="h-20 w-20 rounded-full flex items-center"
      src={Routes.static_path(@socket, "/images/check.webp")}
      alt="">
    </div>
  </div>

</.modal>
"""
```

No problem! The unused slots are empty, but not error-raisingly undefined.

### Slot attributes

We can give named slots _attributes_ in much the same way that we pass assigns to regular function components, and these attributes can be accessed from inside the function component.

We’re going to use slot attributes to pass custom CSS classes to each of the footer buttons. Here we give the `@confirm` and `@cancel` slots each an attribute called `classes`:

```elixir
<!-- named slot: confirm 1 -->
<:confirm classes="bg-green-400 rounded-full text-slate-50 text-sm p-2">
  Return to profile
</:confirm>

<!-- named slot: cancel 1 -->
<:cancel classes="bg-emerald-400 rounded-full text-slate-50 text-sm p-2">
  Back to index
</:cancel>
```

Now, to get at the attributes of our named slots, we have to take into account that it’s possible to define [multiple entries for the same named slot](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-named-slots-with-attributes). When we call `render_slot/2`, it simply renders all the entries for the slot name. But inside the slot’s assign, there’s a list of attribute maps: one for each entry. Even if there’s only one entry, and so one attributes map, we use a [`for` loop](https://elixir-lang.org/getting-started/comprehensions.html) to get into the list and access it.

To demonstrate: For every slot entry named `@confirm`, we’ll make a button whose class is the string-interpolated value of the `classes` attribute of that entry. We’ll do the same again for `@cancel`.

```elixir
<!-- FOOTER -->
<div class="modal-footer">
  <%= for confirm <- @confirm do %>
    <button class={"#{confirm.classes}"}><%= render_slot(@confirm) %></button>
  <% end %>

<%= for cancel <- @cancel do %>
    <button class={"#{cancel.classes}"}><%= render_slot(@cancel) %></button>
  <% end %>
</div>
```

Since there’s only one slot entry for each name, we’ll only get one button from each!

Our slot-specific classes have been applied, giving the buttons different colors!

### Passing assign values to the caller’s slot

Sometimes we’ll want to use data from an assign passed to the function component, within content that’s provided by a slot. The component has to pass that value into the slot to be rendered. We can do this by giving it as an argument to `render_slot/2`.

Suppose our function component takes a `@user` assign with data about the current user in a struct. We want to display `@user.name` from that struct in the body of the modal.

In the function definition, we’ll pass `render_slot/2` the `@user.name` as an argument.

```elixir
<!-- MODAL BODY -->
<div class="mt-10">
  <p id={"#{@id}-content()}" class="text-sm text-gray-500">
  <%= render_slot(@body, @user.name) %>
  </p>
</div>
```

In the following slot definition, the `let` argument binds the incoming `@user.name` data to the name `username`, so we can ask for it to be interpolated using `<%= username %>`. We have to use a named slot for the body this time, so we can use `let`.

```elixir
<:body let={username}>
  <div class="text-center justify-center items-center">

<h1 class="text-green-600">Hey, <%= username %>!</h1>

<p>Your settings have been <strong>successfully</strong> saved</p>
    <div class="flex items-center justify-center">
      <img class="h-20 w-20 rounded-full flex items-center"
      src={ Routes.static_path(@socket, "/images/check.webp") }
      alt="">
    </div>
  </div>
</:body>
```

Our modal is now super-duper customized, with the current user’s name provided to a slot from an assign!

It’s your turn to build something with the power of LiveView function components!
