Building a cool frontend with liveview
In this article, I want to introduce an interesting concept: how LiveView, in today’s landscape, provides us with the power we’d typically associate with any JavaScript framework in the market. This allows us to create applications in Phoenix with minimal or almost no JavaScript.
An example of this (albeit incomplete) can be seen in this site. It’s based on a Tailwind
template and built on top of NextJs
.
When I started this project with the idea of exploring the componentization power of the latest LiveView version, I kept the component structure similar to what you would find in a NextJS/React
project. However, I followed the recommendations of the creator of Phoenix
. So, all the global site components can be found in the lib/iagocavalcante_web/components
folder. But contrary to the recommendation, the first important change I made was within the core_components.ex
module.
In that file, you’ll find the default components that come with Phoenix
, already integrated with Tailwind
in the latest versions. When I say “components,” I mean that literally all the component structures are in this file. However, in order to achieve better organization, I decided to create separate components, making maintenance and easy access much simpler.
Here’s an example of one of my site’s language-switching components:
defmodule IagocavalcanteWeb.ToggleLocale do
use Phoenix.Component
import IagocavalcanteWeb.Gettext
def toggle_locale(assigns) do
~H"""
<button
phx-click="toggle_locale"
phx-value-locale={@locale}
type="button"
aria-label="Toggle locale"
class="group rounded-full bg-white/90 px-3 py-2 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur transition dark:bg-zinc-800/90 dark:ring-white/10 dark:hover:ring-white/20"
>
<img
src={"/images/flags/#{@locale}.png"}
class="h-6 w-6 fill-zinc-700 stroke-zinc-500 transition dark:fill-teal-400/10 dark:stroke-teal-500"
/>
</button>
"""
end
end
To make it available to all our LiveViews, you only need to add the following code within core_components
:
defmodule IagocavalcanteWeb.CoreComponents do
@moduledoc """
Provides core UI components.
The components in this module use Tailwind CSS, a utility-first CSS framework.
See the [Tailwind CSS documentation](https://tailwindcss.com) to learn how to
customize the generated components in this module.
Icons are provided by [heroicons](https://heroicons.com), using the
[heroicons_elixir](https://github.com/mveytsman/heroicons_elixir) project.
"""
use Phoenix.Component
alias Phoenix.LiveView.JS
import IagocavalcanteWeb.Gettext
#...
#...
defdelegate toggle_locale(assigns), to: IagocavalcanteWeb.ToggleLocale, as: :toggle_locale
end
With that in place, within our lib/iagocavalcante_web/components/layouts/app.html.heex
, we can call the component we created and pass the properties it expects, much like props
in React
, Vue
, and similar frameworks.
<div class="fixed inset-0 flex justify-center sm:px-8">
<div class="flex w-full max-w-7xl lg:px-8">
<div class="w-full bg-white ring-1 ring-zinc-100 dark:bg-zinc-900 dark:ring-zinc-300/20">
</div>
</div>
</div>
<div class="relative">
<.header>
<:nav_items>
<.nav_item link={gettext("/about")} text={"#{gettext("About", lang: @locale)}"} active_item={@active_tab} />
<.nav_item
link={gettext("/articles")}
text={"#{gettext("Articles", lang: @locale)}"}
active_item={@active_tab}
/>
<.nav_item
link={gettext("/projects")}
text={"#{gettext("Projects", lang: @locale)}"}
active_item={@active_tab}
/>
<.nav_item
link={gettext("/speaking")}
text={"#{gettext("Speaking", lang: @locale)}"}
active_item={@active_tab}
/>
<.nav_item link={gettext("/uses")} text={"#{gettext("Uses", lang: @locale)}"} active_item={@active_tab} />
</:nav_items>
<:toggle_items>
<.toggle_locale locale={@locale} />
<.live_component module={IagocavalcanteWeb.ToggleTheme} theme={@theme} id="theme" />
</:toggle_items>
</.header>
<main>
<%= @inner_content %>
</main>
<.footer />
</div>
In the example above, our lib/iagocavalcante_web/components/layouts/app.html.heex
even uses slots to simplify the graphical interface composition.
That’s it, folks! I hope you enjoyed the post. Feedback and comments are always welcome. You can send emails to iagocavalcante@hey.com
.
Until next time.