Simple Phlex Class Variants

Create 10's of components with slight variations in their class names

When building out components in Phlex, it’s very common to need slight variations on a single component to render different classes. For example, a button component might start like this:

class Components::Button < Components::Base
  def view_template(&)
    div(class: "btn", &)
  end
end

Then you call it from your views using Phlex Kit like this:

Button { "Watch the intro video" }

Life is good until your user asks you for a primary & secondary button.

Class variants without gems

In Ruby, there’s a good chance a gem has already solved the problem. That’s true for this problem too with gems like Phlex Variants and the Class Variant gems, but you might not need those.

Subclassing

Since Phlex is just Ruby, we could subclass our button with different classes.

class Components::Button < Components::Base
  def view_template(&)
    div(class: ["btn", classes], &)
  end

  def classes
    nil
  end
end

class PrimaryButton < Button
  def classes
    "btn-primary"
  end
end

class SecondaryButton < Button
  def classes
    "btn-secondary"
  end
end

That feels like a lot of lines of code to have different button variants, so let’s tighten it up a bit.

Endless methods

A little-known feature in newer versions of Ruby is endless methods. It lets us express classes with fewer lines of code.

class Components::Button < Components::Base
  def view_template(&)
    div(class: ["btn", classes], &)
  end

  def classes = nil
end

class PrimaryButton < Button
  def classes = "btn-primary"
end

class SecondaryButton < Button
  def classes = "btn-secondary"
end

Ah, better! It’s easier to read too. When a future developer opens the file, they can quickly see the different types of buttons and the class strings used to style them.

The remaining thing I’m not a huge fan of are classes with namespaces smooshed into them, in our case SecondaryButton. Let’s clean that up to make the hierarchy more clear.

Nested classes

My first iteration of class variants might stay in one file. In this case I’d nest the primary and secondary buttons in the ./app/components/buttons.rb file.

class Components::Button < Components::Base
  def view_template(&)
    div(class: ["btn", classes], &)
  end

  def classes = nil

  class Primary < self
    def classes = "btn-primary"
  end

  class Secondary < self
    def classes = "btn-secondary"
  end
end

Now, with Phlex Kit, we’d render our buttons like this:

Button::Primary { "Pre-order Phlex on Rails course" }
Button::Secondary { "Watch the intro video" }

Breaking these class variants out into separate files at this point might make it more difficult for a person coming into the project to grok the function of the component.

Limitations

As you start adding more aspects to your class variants, like size & color, you’d have to generate a constant per variant. This could be implemented with a macro that would generate class names for each variation, but then it would become order dependent and make more sense implemented as a parameterized function in a class.

I recommend using endless methods and nested classes in situations where you know there are only tens of variants with few permutations. For more variants, consider using a parameterized function or a macro to generate the class names dynamically or reach for the Phlex Variants and Class Variant gems.

Do you want to learn Phlex 💪 and enjoy these code examples?

Support Beautiful Ruby by pre-ordering the Phlex on Rails video course.

Order the Phlex on Rails video course for $379 $289