Subscribe to access all episodes. View plans →
Published May 31, 2022
Elixir 1.12
Phoenix 1.6
Follow along with the episode starter on GitHub
The word ‘Petal’ in the relatively new Petal Stack, is an acronym that stands for Phoenix - Elixir - Tailwind CSS - Alpine.js - and LiveView. And Petal Components is an open source component library that supercharges this stack. Written in HEEX and styled with Tailwind, Petal Components make it easy to build beautiful web applications by providing pre-built components you can easily drop into your application and customize. In this episode, we’ll get an introduction to Petal Components.
If you’re starting a new Phoenix application and want to get started quickly with the PETAL stack using Petal Components, I’d suggest taking a look at the Petal Boilerplate project here. It comes pre-installed with Tailwind CSS, Alpine JS, as well as the Petal Components Library. However, in this episode, we won’t be starting a new project. Instead, we’ll update this Phoenix LiveView project to use Petal Components. Let’s get started. We’ll go to Hex and grab the latest version of petal_components
. Then we’ll open our Mixfile and add it as a dependency. Because Petal Components use Tailwind CSS, we’ll add the tailwind
package, setting the runtime to development.
mix.exs
...
defp deps do
...
{:petal_components, "~> 0.16"},
{:tailwind, "~> 0.1", runtime: Mix.env() == :dev},
...
end
...
Then let’s go to the command line and get our new dependencies.
$ mix deps.get
...
New:
petal_components 0.16.0
tailwind 0.1.5
With those downloaded let’s configure tailwind. We’ll go to our config.exs
and paste the config into our tailwind
config. For our project, we’ll be using Tailwind version 3.
config/config.exs
...
config :tailwind,
version: "3.0.12",
default: [
args: ~w(
--config=tailwind.config.js
--input=css/app.css
--output=../priv/static/assets/app.css
),
cd: Path.expand("../assets", __DIR__)
]
...
Tailwind has a watcher that will scan your project’s HTML code for classes, which it will then combine and include in your final CSS file. While in development, we need to allow Tailwind to do this on the fly. Let’s open our dev.exs
find the watchers list in our Endpoint config and update it.
config/dev.exs
...
config :teacher, TeacherWeb.Endpoint,
...
watchers: [
# Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
]
...
Now we can install tailwind.
$ mix tailwind.install
...
Great, it was installed successfully! Because we’re using Tailwind, we won’t need the default styles.css
stylesheet, so we can go ahead and remove that.
Then let’s open our app.css
and when we installed tailwind, it added imports for tailwind base, components, and utilities. Since those are now being imported, we can go ahead and remove all of our old CSS replacing them with a couple LiveView specific styles.
assets/css/app.css
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
/* LiveView specific classes for your customization */
.phx-no-feedback.invalid-feedback,
.phx-no-feedback .invalid-feedback {
display: none;
}
.phx-click-loading {
opacity: 0.5;
transition: opacity 1s ease-out;
}
.phx-disconnected{
cursor: wait;
}
.phx-disconnected *{
pointer-events: none;
}
With that updated let’s go back to our Mixfile and update the assets.deploy
alias to include the Tailwind CSS build step. Now our CSS will get built when we deploy our app.
mix.exs
...
defp aliases do
[
...
"assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]
...
]
end
...
Now when we installed Tailwind, it created a tailwind config, let’s open that.
We’ll need to update it to include Petal Components. Here we can also set any primary and secondary colors we want to use. Let’s use tailwinds colors here - blue for our primary color and pink for our secondary. Because our application has a form we want to style, we’ll want to ensure the tailwind forms plugin is included.
assets/tailwind.config.js
const colors = require("tailwindcss/colors");
module.exports = {
content: [
'./js/**/*.js',
'../lib/*_web.ex',
'../lib/*_web/**/*.*ex',
'../deps/petal_components/**/*.*ex'
],
theme: {
extend: {
colors: {
primary: colors.blue,
secondary: colors.pink
}
},
},
plugins: [
require('@tailwindcss/forms')
]
}
Now let’s open the teacher_web.ex
module and in the view_helpers
let’s include use PetalComponents
, which makes it easier to call components in our templates.
lib/teacher_web.ex
...
defp view_helpers do
quote do
...
use PetalComponents
end
end
...
With those changes let’s go to the command line and start up our server.
$ mix phx.server
...
When we do we get an error - the modal
function is being imported from both PetalComponents.Modal
and TeacherWeb.LiveHelpers
. This is because PetalComponents comes with its own modal that we’ll want to use.
So open the LiveHelpers
module and we’ll remove the modal
function.
lib/teacher_web/live/live_helpers.ex
defmodule TeacherWeb.LiveHelpers do
end
With that update, let’s try to start the server again. And great it starts up.
$ mix phx.server
...
If we go check our albums page it’s not much to look at without any styling, so let’s update that now. We’ll go to our live.html.heex
layout and in it, we can see there’s a container
along with our info
and error
flash messages. If we open the Petal Components docs, we see there’s an alert component as well as a container component. Each component shows you how to use it in a template, as well as what properties are available to customize it.
Let’s update our layout to use these components. We won’t go through all component properties in this episode, but let’s walk through our changes here. We have this container
component, which has a class
property where we can give the component any CSS classes. Here we’re including the my-10
to add a margin to the top and bottom of the component. Then we’re nesting two alert
components in our container
component. Our first alert is the info
alert, with the color info
, a class
, a label
, and then the phx-click
binding and phx-value-key
attribute. Then a second alert
for error messages.
Template path: lib/teacher_web/templates/layout/live.html.heex
<.container class="my-10">
<.alert
color="info"
class="mb-5"
label={live_flash(@flash, :info)}
phx-click="lv:clear-flash"
phx-value-key="info"
/>
<.alert
color="danger"
class="mb-5"
label={live_flash(@flash, :error)}
phx-click="lv:clear-flash"
phx-value-key="error"
/>
<%= @inner_content %>
</.container>
Then let’s go to our album’s index.html.heex
template and we’ll update our page heading to use the h2
component and the link to our new album modal to use the button
component. All our albums are displayed below in a table. Luckily Petal Components has components just for tables - let’s go back to our template and update it to use these. For each album we have our album actions, to edit and delete an album. Let’s style these with the button component. We’ll update our modal component to work with Petal, replacing the return_to
property with the title
. With that our index template has been updated to use components.
Template path: lib/teacher_web/live/album_live/index.html.heex
<div class="mb-8 sm:flex sm:justify-between sm:items-center">
<div class="mb-4 sm:mb-0">
<.h2 class="!mb-0">Listing Albums</.h2>
</div>
<div>
<.button link_type="live_patch" label="New Album" to={Routes.album_index_path(@socket, :new)} />
</div>
</div>
<%= if @live_action in [:new, :edit] do %>
<.modal title={@page_title}>
<.live_component
module={TeacherWeb.AlbumLive.FormComponent}
id={@album.id || :new}
title={@page_title}
action={@live_action}
album={@album}
return_to={Routes.album_index_path(@socket, :index)}
/>
</.modal>
<% end %>
<.table>
<thead>
<.tr>
<.th>Title</.th>
<.th>Artist</.th>
<.th>Summary</.th>
<.th>Year</.th>
<.th></.th>
</.tr>
</thead>
<tbody id="albums">
<%= for album <- @albums do %>
<.tr id={"album-#{album.id}"}>
<.td><%= album.title %></.td>
<.td><%= album.artist %></.td>
<.td><%= album.summary %></.td>
<.td><%= album.year %></.td>
<.td class="text-right whitespace-nowrap">
<.button
color="white"
variant="outline"
size="xs"
link_type="live_patch"
label="Edit"
to={Routes.album_index_path(@socket, :edit, album)}
/>
<.button
color="danger"
variant="outline"
link_type="a"
to="#"
size="xs"
label="Delete"
phx-click="delete"
phx-value-id={album.id}
data-confirm="Are you sure?"
/>
</.td>
</.tr>
<% end %>
</tbody>
</.table>
Now we just need to go to our AlbumLive.Index
LiveView module and the Petal Component modal expects us to handle the “close_modal” action, so let’s add a handle_event
callback for that, pattern matching on “close_modal”. When the modal is closed we’ll send the user back to the album index page.
lib/teacher_web/live/album_live/index.ex
...
@impl true
def handle_event("close_modal", _, socket) do
{:noreply, push_patch(socket, to: Routes.album_index_path(socket, :index))}
end
...
Now let’s go back to the browser and great we see our changes! Our app looks a lot better! But if we edit an album, the modal works, but the form could look a lot better. Let’s update that now. We’ll go to the form_component.html.heex
template and it’s already using a form
component, so we’ll just need to update it to use the form_field
and button
components.
Template path: lib/teacher_web/live/album_live/form_component.html.heex
<div>
<.form
let={f}
for={@changeset}
id="album-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save">
<.form_field type="text_input" form={f} field={:title} />
<.form_field type="text_input" form={f} field={:artist} />
<.form_field type="textarea" form={f} field={:summary} />
<.form_field type="number_input" form={f} field={:year} />
<div class="flex justify-end">
<.button
type="submit"
phx_disable_with="Saving..."
label="Save"
/>
</div>
</.form>
</div>
Let’s go back to the browser and our form modal looks a lot better. With very little effort we were able to get our app updated to use Tailwind and Petal Components.
Now if you want to save even more development time there’s a paid version of Petal, which helps support the continued development of Petal Components and comes with some pretty great features like authentication, social logins, layouts, and HTML email components all built-in. Petal Pro also has modified versions of both the phx.gen.html
and phx.gen.live
scaffold commands to save you even more time.
Romenig Lima Damasio
2 years agovery nice components!
Alekx
2 years agoI agree - they’ve done a great job with this project!
Romenig Lima Damasio
2 years agoIt is necessary to install the AlpineJS as well?
Alekx
2 years agoIf you want have the full PETAL stack and use the functionality provided by AlpineJS you would need to install it. Since I was just using the components in this episode I didn’t need AlpineJS.
Also, they do have a Petal Boilerplate project that comes with AlpineJS pre-installed.