Basic Phoenix Components

DANA 325 P G U O K I B V Q X D H L W T S E N M R F A C J

Objectives

not yet graded
  • Recreated the ui component library with the button from the video (2 Points)

  • Implemented Flowbite Badge Component as a Phoenix Component with an attribute color that allows for all eight flowbite color variants. (2 Points)

  • large attribute will enlarge badges correctly, while not having large attribute will leave the badges at default sizes. (1 Point)

  • 100% Test Coverage on your badge component (2 Points)

In our videos we have learned that phoenix components are nothing but functions that take in an assigns parameter and return heex (HTML + EEX (embedded elixir)).

In our templates we spawn a phoenix component like any other html element:

<.button color="alternative">Click Me</.button>

Our button is implemented in our custom ui library, in its own file. I suggest you copy/create this component in your folder.

defmodule AppWeb.Components.UI.Button do
  use Phoenix.Component

  @doc """
  Renders a button.

  ## Examples

      <.button>Send!</.button>
      <.button phx-click="go" class="ml-2">Send!</.button>
  """
  attr :color, :string, default: "default", values: ~w(default alternative)
  attr :type, :string, default: "button"
  attr :class, :string, default: nil
  attr :rest, :global, include: ~w(disabled form name value)

  slot :inner_block, required: true

  def button(assigns) do
    ~H"""
    <button
      type={@type}
      class={[
        "py-2.5 px-5 me-2 mb-2 focus:ring-4 font-medium text-sm rounded-lg focus:outline-none",
        @color == "default" &&
          "text-white bg-blue-700 hover:bg-blue-800 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800",
        @color == "alternative" &&
          "text-gray-900 bg-white border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700",
        @class
      ]}
      {@rest}
    >
      {render_slot(@inner_block)}
    </button>
    """
  end
end

To make our component load we have created the UI module:

defmodule AppWeb.Components.UI do
  @moduledoc """
  Provides resuable UI components.

  Basic UI components are derived from [Flowbite](https://flowbite.com/).
  Icons are provided by [Flowbite](https://flowbite.com/icons/). See `icon/1` for usage.
  """

  defmacro __using__(_) do
    quote do
      import AppWeb.Components.UI.{
        Button
      }
    end
  end
end

The __using__ macro logic allows us insert use AppWeb.Components.UI in any module. This will make our entire UI Component library accessible. We have done that in AppWeb.ex:

defmodule AppWeb do
  # ...

  defp html_helpers do
    quote do
      use AppWeb.Components.UI
      # ...
    end
  end

  # ...
end

Finally we have removed the obsolete button function from AppWeb.CoreComponents.

Objective 1 - Building a UI Component Library

Display a <.button> element on any of your pages using your brand new UI Component Library. We are going to expand and build upon this library in the future so that is an important milestone.

Objective 2 - Implementing Badges

To practice core compentents further our main task today is to implement the default Flowbite badges and convert them to reusable phoenix components.

After you have added a AppWeb.Components.UI.Badge module the following should all display correct badges:

<.badge color="default">Default</.badge>
<.badge color="dark">dark</.badge>
<.badge color="red">red</.badge>
<.badge color="green">green</.badge>
<.badge color="yellow">yellow</.badge>
<.badge color="indigo">indigo</.badge>
<.badge color="purple">purple</.badge>
<.badge color="pink">pink</.badge>

Make sure to display your badges on one of your pages.

To get credit your badges must look like the ones on the documentation: Badges

Objective 3 - Large Badges

To make our badges more versatile we will also implement Flowbites badge sizing attribute:

<.badge color="default" large>Default</.badge>

Notice the large attribute doesn't seem to have a value. In fact it has. Whenever we put an attribute without ="string" or ={elixir code} it defaults to true. The following two are the same:

<.badge large>Default</.badge>
<.badge large={true}>Default</.badge>

When you define your attr as a boolean make sure to set the default value to false, as to not accidentally make all badges large.

Add one or two large badges to your page to demonstrate you have completed that part.

Objective: Testing Badges

The final points will be earned if you can demonstrate 100% test coverage on your coverage report for your badges module. Testing phoenix components is straightforward but watch the video to understand why we will need to change formatter.exs to this:

[
  import_deps: [:ecto, :ecto_sql, :phoenix],
  subdirectories: ["priv/*/migrations"],
  plugins: [Phoenix.LiveView.HTMLFormatter],
  inputs: ["*.{heex,ex,exs}", "{config,lib}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
]

To get credit for this objective running this command should pass all tests:

mix test test/app_web/components/ui/badge_test.exs

As a reminder, here is my testfile that I had for my phoenix buttons:

defmodule AppWeb.Components.UI.ButtonTest do
  use AppWeb.ComponentCase

  import AppWeb.Components.UI.Button

  test "default button" do
    assigns = %{}

    html =
      rendered_to_string(
        ~H"""
        <.button class="custom-class" id="ID">Click</.button>
        """
      )

    assert html =~ "id=\"ID"
    assert html =~ "type=\"button"
    assert html =~ "custom-class"
    assert html =~ "text-white bg-blue-700 hover:bg-blue-800"
    assert html =~ "py-2.5 px-5 me-2 mb-2"
    assert html =~ "Click"
    assert html =~ "</button>"
  end

  test "default button with custom classes" do
    assigns = %{}

    html =
      rendered_to_string(
        ~H"""
        <.button color="default" class="custom-class">Click</.button>
        """
      )
    assert html =~ "type=\"button"
    assert html =~ "text-white bg-blue-700 hover:bg-blue-800"
    assert html =~ "Click"
  end

  test "alternative submit button" do
    assigns = %{}

    html =
      rendered_to_string(
        ~H"""
        <.button type="submit" color="alternative">Click</.button>
        """
      )

    assert html =~ "type=\"submit"
    assert html =~ "py-2.5 px-5 me-2 mb-2"
    assert html =~ "text-gray-900 bg-white border border-gray-200"
    assert html =~ "</button>"
  end
end

Here is the ComponentCase module I added:

defmodule AppWeb.ComponentCase do
  @moduledoc """
  This module defines the test case to be used by
  tests that use simple function components.
  """
  use ExUnit.CaseTemplate

  using do
    quote do
      import Phoenix.Component
      import Phoenix.LiveViewTest
    end
  end
end
Copyright © 2025 Alexander Fuchsberger, Bucknell University. All rights reserved.