Why Ruby on Rails Needs Components

Why Ruby on Rails Needs Components

I previously wrote a bit about What Rails Components and why don't we have them are at a very high level. Rails Components are shareable, encapsulated, and interoperable pieces of functionality that can be dropped into your Rails application. They are essentially the equivalent of React Components, styled, functional, interactive pieces of frontend that you can just drop into your application and they work.

Just as a reminder, the goal of the project is to enable this sort of equivalency:

import { Drawer } from 'vaul';

function MyComponent() {
  return (
    <Drawer.Root>
      <Drawer.Trigger>Open</Drawer.Trigger>
      <Drawer.Portal>
        <Drawer.Content>
          <p>Content</p>
        </Drawer.Content>
        <Drawer.Overlay />
      </Drawer.Portal>
    </Drawer.Root>
  );
}
<%= render_drawer do %>
  <%= drawer_trigger "Open" %>
  <%= drawer_content do %>
    <p>Content</p>
  <% end %>
<% end %>

Both would produce:

Drawer

But Why?

As I mentioned yesterday, solutions close to this exist in framework agnostic javascript libraries and potentially in native web components. I just don't think either of those are the right solution for Rails as javascript libraries still require potentially cumbersome integration and web components are introducing a new layer to your application. If Ruby on Rails is going to continue to grow in terms of adoption, it needs to get serious about enabling beautiful frontend experiences.

The benefit of Ruby on Rails is it's development speed which remains consistent as your application grows (to some extent, if you're building the next Github, you might run into some challenges). It's a great, if not the best, backend MVC framework out there. It was the original high-velocity web framework and quickly became, and to some extent probably still is, the best choice for the majority of content-based SaaS applications if not more. And that's because you can build those applications fast. But you can't make them look good fast. I mean you can, but that's your job and the framework doesn't help you.

A First Class Frontend

There's a lot of innovation and power in the Rails frontend, don't get me wrong. I think Hotwire, Turbo, and Stimulus are just incredible additions to the Rails ecosystem and provide the framework for building incredible frontend experiences. However, that's all they provide, the framework, they don't provide the experience. There are awesome libraries for Stimulus like Stimulus Components that use this framework to offer drop-in controllers that can provide common interactivity to your application. As long as you implement the markup and DOM as they intend, you can get these interactions.

<div data-controller="popover">
  This is my Github card available on
  <a href="/profile" data-action="mouseenter->popover#show mouseleave->popover#hide"> Github </a>

  <template data-popover-target="content">
    <div data-popover-target="card">
      <p>This content is in a hidden template.</p>
    </div>
  </template>
</div>

Popover

That's not bad. But, libraries like this, which I think are the most "drop in" friendly, still leave you to implement quite the specific markup. They also don't really come with styles and leave you to have a sense of how to style the elements, which is nice in terms of customizability but leaves something to be desired in terms of out of the box awesomeness.

What New Developers Want

Ultimately, as a new developer, when considering a web framework to use, I think a lot of them are seeing all the shiny components that React has to offer and thinking to themselves, "if I use React, my application can look and feel like that." I can't remember who recently said this, but shadcn/ui is almost a reason to use React and Next.js. It's that good of a base frontend. There's simply no equivalent concept in the Rails world. Point me to a Rails library that makes me think, this library alone justifies me using Rails. And you'd have to think of that just by looking at that library. After all, in the end of the day, it's easier to conceive of how you want your application to look and feel than how you want it to work and be implemented.

If Rails wants new developers, we need to give them a reason they can see to use Ruby on Rails.

Reasons You Can See to Use Ruby on Rails

Look, there's got to be more that we can offer to the frontend's markup than <%= content_tag %> or <%= form_for %>. ActionView is definitely a batteries not included framework. That's okay, that's what it intends to be, simple markup shortcuts for a frontend, not a frontend solution. You're suppose to use it to build your frontend, it is not suppose to provide you with a frontend. And that's the reason you can see that we need to provide the Ruby on Rails ecosystem: A complete markup solution for the frontend.

Providing a Frontend Solution

To scope this, what I mean by a complete markup solution for the frontend is the markup of your application along with styles provided by css classes, whether utility classes such as tailwindcss offers or utility classes such as bootstrap offers. There needs to be a mechanism, a strong pattern, that allows developers to share the idea of a fully styled component. To take a common example, imagine a Product Card, in fact, imagine this one from HyperUI.

Product Card

<a href="#" class="group relative block overflow-hidden">
  <button
    class="absolute end-4 top-4 z-10 rounded-full bg-white p-1.5 text-gray-900 transition hover:text-gray-900/75"
  >
    <span class="sr-only">Wishlist</span>
    <svg
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
      stroke-width="1.5"
      stroke="currentColor"
      class="h-4 w-4"
    >
      <path
        stroke-linecap="round"
        stroke-linejoin="round"
        d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z"
      />
    </svg>
  </button>
  <img
    src="https://images.unsplash.com/photo-1599481238640-4c1288750d7a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2664&q=80"
    alt=""
    class="h-64 w-full object-cover transition duration-500 group-hover:scale-105 sm:h-72"
  />

  <div class="relative border border-gray-100 bg-white p-6">
    <span
      class="whitespace-nowrap bg-yellow-400 px-3 py-1.5 text-xs font-medium"
    >
      New
    </span>

    <h3 class="mt-4 text-lg font-medium text-gray-900">Robot Toy</h3>

    <p class="mt-1.5 text-sm text-gray-700">$14.99</p>

    <form class="mt-4">
      <button
        class="block w-full rounded bg-yellow-400 p-4 text-sm font-medium transition hover:scale-105"
      >
        Add to Cart
      </button>
    </form>
  </div>
</a>

That's a lot of structure and markup for a card. What I've seen in Rails applications, what I think the framework would suggest, is to create something like a products/_card.html.erb partial. Now here's the interesting thing, let's look at what that view directory might look like:

products/
  _card.html.erb
  index.html.erb
  show.html.erb

Here you have two files that are clearly domain specific, the main views, responsible for how your application behaves to index products and to show a product. But then you have this partial that's probably not as domain specific, it's sort of generic, but it's in the same space as those other files. Another example could be more extreme.

account/
_account_dropdown.html.erb
artists/
  _artists_dropdown.html.erb
shared/
  _dropdown.html.erb

In this example, you have a domain specific _artists_dropdown.html.erb, presumably responsible for rendering a dropdown of artists. You also have a somewhat domain specific _account_dropdown.html.erb. And both of these use a shared generic _dropdown.html.erb. This is the pattern I think we need to change. We want to make _dropdown.html.erb a first class citizen in the Rails ecosystem. We want to make it a component and we want that to be easily shareable with another application through a gem or installer.

It might look like a cosmetic change, but what I'm suggesting is a pattern:


app/views/components
  _dropdown.html.erb

Files in there are generic UI units, first class objects in our view domain, that presumably can be copy and pasted from app to app or bundled and made available via a gem. You can render those partials using the standard render command. Those partials can be wrapped within ViewComponent or even Phlex architecture. Or they can be exposed by helpers that help normalize the options and the slots potentially required. Let's look at what a view helper for that product card partial might look like in use:

<%= render_product_card product_path(@product) do %>
  <%= product_title "Robot Toy" %>
  <%= product_price "$14.99" %>
<% end %>

I think that's compelling and clear. The API could change and certainly we can make the helper accept arguments for the content slots instead of representing the slots as capture areas in the block, but the point is, as a new developer I see that code and I see the end result I can get and it's a compelling reason to be using the framework. I can either inherit this themes helpers and views via a gem or install the helpers and views into my app via the gem so that I can edit them later.

This is what shadcn on rails aims to provide. As far as I know, it's the first gem that's saying it will install files into your app that are simply responsible for making structured components that come fully functional and fully styled. We need more of those if we want to capture the interests of new developers.

Did you find this article valuable?

Support Avi Flombaum by becoming a sponsor. Any amount is appreciated!