Partials FX

Partials FX extends Rails' partials to make them quack more like components.

Installation

bundle add partials_fx

Usage

bin/rails generate component Avatar

This creates two files:

  • app/views/components/_avatar_component.html.erb;
  • app/views/components/avatar_component.rb.

They could look like this:

<span id="user-avatar" class="<%= component_class %> <%= avatar_size %>">
  <% if avatar.attached? %>
    <%= image_tag avatar.variant(avatar_variant), alt: alt_text %>
  <% else %>
    <%= tag.span name.first.upcase, class: "initial" %>
  <% end %>
</span>
# app/views/components/avatar_component.rb
class AvatarComponent < PartialsFx::Component
  attribute :user, required: true
  attribute :size, :string, default: "md", inquiry: true
  attribute :variant, :string, default: "thumb", inquiry: true

  def avatar_size
    return "avatar-profile" if variant.profile?

    class_names(
      "avatar-xs": size.xs?,
      "avatar-sm": size.sm?,
      "avatar-md": size.md?,
      "avatar-lg": size.lg?,
      "avatar-xl": size.xl?
    )
  end

  def name
    @name ||= user.decorate.name
  end

  def avatar = profile.avatar

  def alt_text = "Avatar for #{name}"

  def avatar_variant
    variant.thumb? ? :thumb : :profile
  end

  private

  def profile
    @profile ||= user.profile
  end
end

In views:

<%= render AvatarComponent.new user: Current.user %>

CSS modules

A powerful feature of Partials FX is support for “CSS modules”. Meaning that styles are locally scoped by default, preventing style conflicts across components. This allows the use of the same class names in different components without worrying about collisions, as Partials FX automatically generates unique class names, for the parent element, during compilation. CSS modules improve maintainability by creating a clear connection between components and their styles, making it easier to manage complex UI systems.

It works like this. First define a styles block in the class:

class AvatarComponent < PartialsFx::Component
  # …

  styles do
    <<~CSS
      .component {
      }
    CSS
  end

  # …
end

Then add the component_class method to the the component partial:

<span id="user-avatar" class="<%= component_class %> <%= avatar_size %>">
</span>

You can now add any CSS selector in the .component selector, which itself will compile to something like:

/* app/assets/stylesheets/components.partialsfx.css */
.c-9af2b949 {
  --avatar-font-size-fallback:
    clamp(.75rem, calc(var(--avatar-height) * .8), 2rem);
  display: inline flex;
  align-items: center;
  justify-content: center;
  height: var(--avatar-height, 1rem);
  /* … */

  .initial {
    font-size: var(--avatar-font-size, var(--avatar-font-size-fallback));
    font-weight: 600;
    color: var(--avatar-color, var(--base-60));
  }

  &.avatar-profile {}

  /* … */
}

Inside the component partial, component_class will be replaced with the unique selector .c-9af2b949. Because nested selectors are available in CSS this will work smoothly. Keeping your selectors scoped to the one component. Making your app’s CSS way more maintainable.

Make sure to import the automatically-created CSS file from Partials FX into your application.css, e.g.

@layer reset, components, utilities;

/* … */
@import url("./components.partialsfx.css") layer(components);
/* … */