How to create components in Phoenix?

I am using tailwind with Phoenix framework. Currently I have to repeat classes for components like buttons, links, tables etc. to maintain the look & feel.

For example, here’s a link that looks like a button:

    <%= link "New Contact",
      to: Routes.contact_path(@conn, :new),
      class:
        "inline-block
        px-4 py-2
        rounded-lg
        shadow-md
        bg-gray-800
        hover:bg-gray-700
        text-white
        focus:outline-none focus:ring focus:ring-offset-2 focus:ring-primary-main focus:ring-opacity-50
        uppercase tracking-wider font-semibold text-sm"
    %>

Here’s an example of a table cell with the look that I want:

<td class="whitespace-nowrap px-3 py-4 text-base text-gray-900"><%= contact.firstName %></td>

Is there a way to encapsulate these into components so that I don’t have to repeat the styles? Something like:

<LinkButton to={Routes.contact_path(@conn, :new)} />
<TableCell><%= contact.firstName %></TableCell>

https://hexdocs.pm/phoenix_live_view/0.17.7/Phoenix.Component.html

It would be something like this:

defmodule MyAppWeb.Table do
  use Phoenix.Component

  def head(assigns) do
    ~H"""
    ...
    """
  end

  def cell(assigns) do
    ~H"""
    <td class="whitespace-nowrap px-3 py-4 text-base text-gray-900">
      <%= render_slot(@inner_block) %>
    </td>
    """
  end

And you would use it as such in your HEEX: (assuming MyAppWeb.Table is aliased)

<div>
  ...
  <Table.head items={["First Name", "Age", ...]} />
  <%= for contact <- @contacts do %>
    <Table.cell><%= contact.first_name %></Table.cell>
    <Table.cell><%= contact.age %></Table.cell>
    ...
  <% end %>
  ...
</div>
2 Likes

You could also import the module as suggested here :slight_smile:

defmodule MyAppWeb do
  defp view_helpers do
    quote do
      import MyAppWeb.ButtonComponent
    end
  end
end

Furthermore I’d recommend using semantic class names (.button) in combination with Tailwind’s @apply or theme(...) over utility classes for two reasons:

  1. you won’t run into specificity issue once you try to pass a class to the component (<.button class="p-2">... ← this could otherwise conflict with a p-3 utility class within the component)
  2. source code is a lot easier to read (especially when you have a lot of conditionals)

See my explanation here: "Petal Components" package - a set of Tailwind styled components written in HEEX - #45 by thomasbrus

Thanks @LostKobrakai, @tomkonidas & @thomasbrus for your suggestions. For now, I am going with @tomkonidas’s suggestion because it looks simpler to me. Later I can try @thomasbrus’ suggestions.