Why components?

Components make it easy to build a more consistent user interface, which ends up feeling much higher quality to your users. Additionally, shipping new features with a library of components is much easier than building everything from scratch or wrestling with various partials.

A few components in a production Rails application

Breadcrumbs are useful navigation elements for providing a hierarchy of pages your users can navigate through.

In Phlex, a breadcrumb component could be called like this:

class Show < AccountLayout::Component
  attr_writer :item

  def title = @item.name
  def icon = @item.icon
  def subtitle
    # Render the Breadcrumb components
    Breadcrumb do
      # Add a `crumb` with a link.
      it.crumb { show(@account, :name) }
      # Items are in a tree, so we navigate through that.
      @item.ancestors.reverse.each do |item|
        it.crumb { show(item, :name) }
      end
      # And finally, the leaf of the tree is rendered as a link
      it.crumb { show(@item, :name) }
    end
  end
end

From ERB, a Phlex component could be called and rendered like this:

<%= render Breadcrumb.new do %>
  <% it.crumb { show(@account, :name) } %>
  <% it.crumb do %>
    <%= link_to(@item, :name) %>
  <% end %>
<% end %>

The component

The component itself is pretty simple, wrapping a ul > li in a DaisyUI breadcrumb class and adding some padding.

class Components::BreadcrumbComponent < Components::Base
  # Phlex renders whatever is defined in `view_template`
  def view_template
    div(class: "breadcrumbs m-0 p-0") do
      ul do
        # The "block" of the rendered is displayed here.
        yield
      end
    end
  end

  # Crumb can be called from the `yield` block and render an `li` element.
  def crumb(&)
    li(&)
  end
end

We’ll get more into how the component actually works, but what’s impressive is how concisely the Phlex component abstracts away the ul and li elements, allowing you to focus on the content of each breadcrumb item. If the implementation needed to change in the future, the HTML tags could be swapped out and the call sites of the component probably wouldn’t have to change.

Build a UI kit for your app

As your application grows, you’ll begin developing a unique UI component library that reflects your brand and meets the needs of your users. You can snap these components together to build complex interfaces quickly and consistently. In this case, I render a page title component inside of a breadcrumb component block.

class Show < AccountLayout::Component
  # ...
  def subtitle
    Breadcrumb do
      it.crumb {
        # Render a page title component inside of the breadcrumb
        render Components::PageTitleComponent.new(
          title: @item.name,
          icon: @item.icon,
          subtitle: @item.description
        )
      }
      # ...
      @item.ancestors.reverse.each do |item|
        it.crumb { show(item, :name) }
      end
      # ...
      it.crumb { show(@item, :name) }
    end
  end
end

Easier to reason through for highly stateful user interfaces

Many applications have complex user interfaces that require a lot of state management. Components make it easy to reason about the state of your application by breaking it down into smaller, more manageable pieces that are all in one place. Here’s an example of a complex view in Thingybase made up of many different Phlex components.

class Item < Show
  def view_template
    DataView @item do |it|
      it.field(:created_at)
      it.field(:updated_at)
      it.field(:user) { @item.user.name }
      if @item.expires_at
        it.field(:expires_at)
      end
    end
  end

  def action_template
    div(class: "join") do
      LinkButton(edit_item_path(@item), class: "join-item") { "Edit" }
      LinkButton(edit_item_icon_path(@item), class: "join-item") { "Change icon" }
      LinkButton(label_path(@item.label), class: "join-item") { "Label" }
    end

    Menu title: "More..." do |it|
      it.item do
        link_to(new_item_copy_path(@item)) { "Copy" }
      end

      it.item enabled: @account.loanable_list.present? do
        link_to(new_item_loanable_path(@item)) { "Loan" }
      end

      it.item enabled: @account.move.present? do
        link_to(@item.movement ? movement_path(@item.movement) : new_item_movement_path(@item)) { "Move" }
      end

      it.item do
        delete(@item, confirm: "Are you sure?") { "Delete" }
      end
    end
  end
end

The class names map directly to files in the ./app/components directory, which makes it easy to find components that I see in my views.

When there’s an error, I get a highly readable stack trace because the components are plain Ruby objects. I don’t have to figure out which view file or partial is raising an exception.

Integrates into design system workflows

Since components have well-defined interfaces, they’re much easier to hand off to a design team for refinement and integrate into a design system. You can initialize the component with different data and variations in a gallery to document usage guidelines and ensure consistency across your application.

At first, when you start building applications with components, it will feel a bit slow until you get 15–20 solid components defined and used throughout your apps. Once the initial set of components is defined, it becomes much easier to start stacking them into compositions that feel more consistent and higher quality.

Checkout in minutes

Use Apple Pay, Amazon Pay, or your credit card to order this course and we'll email you the receipt.

Purchase video course for $379 $379