Phoenix 1.7 came out this year with a whole host of exciting features, including verified routes and some great built-in Tailwind components. These components are a fantastic start, but they are not made to be a fully general design system. We should expect to modify components to fit our specific needs. However, knowing where to start can be difficult.
In this three-part series, we'll take a fresh Phoenix app and create a working UI using generated components.
In this part, we will add a modal to a page and open it on demand.
Let's get started!
The UI of Our Phoenix App — Setup
The UI we will implement is a list of data and two modals for that data: a create modal and an edit modal. Our example will be a spectacular pet shop called Petacular.
To begin, let's bootstrap the app. If you wish to write the command yourself, first ensure you have the latest Phoenix installer with:
Then run:
This will bootstrap the project and generate the core components we'll use in our examples. To illustrate the various steps in this article, I have included a companion repo with commits for each step. This commit shows us everything that gets generated in the above command. Of particular interest is this module which contains all of the generated components we will be exploring.
The components use Tailwind CSS for styling and are passed attributes and/or slots, as appropriate. We will leverage a few below, but first, we will need a database.
Check out this commit for everything we need to get a db working with docker. This commit adds a migration and creates a few schemas we will use for our example: a list of pets and their preferences. Finally, you can see how this commit makes the home page a LiveView page and introduces some very basic styling.
What Is a Modal in Phoenix?
The initial homepage looks like this (found in lib/petacular_web/pages/home_live.ex
):
I have included a built-in component from the generated PetacularWeb.CoreComponents
module (found in lib/petacular_web/components/core_components.ex
) — a button. The button is defined here and it is simple to use:
We would love for this to open a modal that contains a form for adding a pet. To do
that, let's look at the modal in the PetacularWeb.CoreComponents
module.
This is a function component, meaning it does not have a state. It's just a bundle of
markup and styling. It has some attrs
— for example, attr :show, :boolean, default: false
, and one slot.
What Are attr
s and Slots?
An attr
defines data that's expected to pass. Some attr
s are required, and some have a default
value instead. A slot is space for nested HTML and can be named or not. In essence,
slots let you provide your own markup and appear as children of a function component's element.
We can see the slot is called :inner_block
, a special name for the modal. Everything
inside of the <.modal>
tags will be treated as the :inner_block
slot. So, in the function docs, the modal component looks like this:
The text This is a modal.
is treated as the :inner_block
. In contrast, we can name a
slot. Then we designate the contents of that slot by putting content in between two
tags that use the slot's name. For example, CoreComponents
has a <.header>
component with an optional :subtitle
slot. To provide a subtitle,
we do this:
Making the Modal Appear in Phoenix
The docs for the modal
give us an indication of what the modal needs to look for. We need
to provide an ID that is targeted by the hide_modal
and show_modal
functions. Let's look at how they work first. The show modal is defined like this:
This uses the new(ish) JS
module to execute simple JavaScript functions. It accepts and uses an ID to identify
the element we want to show. Upon appearing, the show modal does some simple animations to ease it
into view and focuses the page onto the first focus-able element, if there is one.
hide_modal
does the same, but in reverse:
Calling the show_modal
Function
With a button click, we call the show_modal
function to make the modal appear. To close the modal, we need a button on the modal itself
that calls hide_modal
. Luckily, this is implemented for us, so we don't need to worry about it.
From our function docs, we see that we need some content inside the modal. Like the button
we used earlier, the modal uses an :inner_block
slot. That means anything inside the
modal tags will appear on the page as the :inner_block
. We can keep this very
simple. Something like the following will work for now:
We can put this inside our homepage and have a click trigger the show_modal
function by
adding phx-click
onto the button we used earlier. Usually, we set phx-click
to a string,
and clicking it sends an event to the backend using that string as the event name. But we
may also provide a JS
function or a chain of them:
Putting it all together, we end up with something like this:
Now when you click the button, the modal will open! 🎉
Wrapping Up
In this post, we used Phoenix 1.7's generated core components to create a modal and open it.
In part two, we'll add the edit form.
Until then, happy coding!
P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, subscribe to our Elixir Alchemy newsletter and never miss a single post!