Component Philosophy — Phlex vs ViewComponent vs Partials
Lesson 2 — Component philosophy: Phlex vs ViewComponent vs partials
The component landscape
Once you accept that you want encapsulated UI components, you have choices. Rails itself doesn’t provide a component abstraction, but the ecosystem has filled the gap in different ways. Understanding the options — and their tradeoffs — will help you make a confident, informed choice in favour of Phlex.
The three approaches worth comparing:
- ERB partials (built-in, no gem required)
- ViewComponent (GitHub’s component library, the established choice)
- Phlex (pure Ruby components, the modern choice)
ERB partials — the baseline
We covered the problems in Lesson 1. But partials do have genuine advantages:
- Zero setup — they’re part of Rails
- Familiar to every Rails developer
- Great tooling support (ERB syntax highlighting, language servers, etc.)
- Fast enough for most applications
- Easy to migrate to incrementally
The verdict: partials are fine for simple, stable UI fragments. A _flash_messages.html.erb or a _footer.html.erb doesn’t need to become a component. The pain arrives when you need parameterised, reusable, testable UI — and that’s where both ViewComponent and Phlex shine.
ViewComponent — the established choice
ViewComponent was built by GitHub and has been battle-tested at enormous scale. It introduces a Ruby class alongside each ERB template:
|
|
<%# app/components/post_card_component.html.erb %>
<div class="post-card">
<h2><%= @post.title %></h2>
<% if @show_author %>
<p>By <%= @post.author.name %></p>
<% end %>
</div>This is a genuine improvement over raw partials. The interface is explicit (the initialize method), instance variables are private to the component, and the component can be unit-tested cleanly.
What ViewComponent does well:
- Strong Rails integration — it works exactly like a view
- Sidecar files: the
.rband.html.erbsit next to each other - Established gem with strong community and GitHub backing
- Stimulus controller sidecar support
- Preview system for component development in isolation
Where ViewComponent has friction:
- You always need two files — the Ruby class and the ERB template
- The template is still ERB, so all the ERB pain (string concatenation for dynamic classes, no IDE type checking on variables passed to the template, etc.) is still present
- Composition and slots are possible but feel bolted on
- The file-per-component discipline means extracting small sub-components has real overhead
- Testing requires a ViewComponent-specific test helper
Phlex — the modern choice
Phlex takes a different position: what if the component and its template were the same Ruby class?
|
|
No ERB file. No separate template. The HTML structure is described using Ruby methods that map directly to HTML tags. div, h2, p — they’re just method calls that take blocks.
This has a cascade of consequences that matter enormously in practice.
Why “everything is a Ruby class” changes everything
Explicit interfaces, for free.
Because a Phlex component is just a Ruby class with an initialize method, its interface is exactly what Ruby gives you: keyword arguments with types, defaults, and required/optional semantics. Your IDE understands it. RubyLSP can autocomplete it. RuboCop can lint it. This is not possible with ERB partials or ViewComponent templates.
Composition is natural.
In Ruby, you compose objects. In Phlex, composing components is the same thing:
|
|
There’s no special slot API to learn. You just render components inside other components, the same way you nest Ruby objects.
Kits make component libraries elegant.
Phlex v2 introduces Kits — modules that expose a collection of components with a clean call syntax:
|
|
This feels like a design system DSL, but it’s just Ruby modules.
Testing is trivial.
Because a Phlex component is a plain Ruby object, testing it requires nothing special:
|
|
No view context mocking, no partial rendering setup. Instantiate, render, assert.
Performance.
Phlex renders HTML at approximately 1.4 GB/s per core on modern hardware. It doesn’t slow down as you extract more components — each component call is a method call, not a file load. For most applications this doesn’t matter much, but it’s worth knowing there’s no performance penalty for aggressive component extraction.
Tailwind CSS is a natural fit.
Tailwind’s utility-first approach is designed for component-based development — but it has a serious problem in a traditional Rails app. Utility classes end up scattered across ERB templates, partials, and helpers alongside the markup. A _post_card.html.erb with class="flex items-center gap-3 p-4 rounded-lg border bg-white shadow-sm hover:shadow-md" repeated across a dozen templates is as hard to maintain as the HTML around it. Changing the card’s appearance means hunting down every partial that renders one.
With Phlex, every component owns its styles. The Tailwind classes for a Button live in Components::Button and nowhere else. Changing the button’s appearance means changing one file. Adding a new variant means adding one entry to a constants hash. The component is the design system — not a convention spread across dozens of files.
This is why Tailwind and Phlex are a natural pairing. They share the same philosophy: co-locate everything that belongs together, make the single source of truth explicit, and eliminate the implicit coupling that makes large codebases hard to change.
In Module 7 we take this further with CSS design tokens — custom properties that let you retheme the entire Phlex::UI library by changing values in one place, with full dark mode support.
The honest comparison
| Partials | ViewComponent | Phlex | |
|---|---|---|---|
| Files per component | 1 | 2 | 1 |
| Explicit interface | No | Yes (Ruby) | Yes (Ruby) |
| Template language | ERB | ERB | Ruby |
| IDE support | Partial | Good | Excellent |
| Unit testable | Awkward | Yes | Yes |
| Composition model | Manual | Slots API | Native Ruby |
| Rails helper access | Implicit | Via helpers | Via adapters |
| Learning curve | None | Low | Low–Medium |
| Incremental adoption | Yes | Yes | Yes |
The case for ViewComponent over Phlex is mostly familiarity with ERB and the existing ecosystem. If your team is deeply comfortable with ERB and has an existing ViewComponent library, the migration cost may not be worth it.
The case for Phlex is everything else: a single file per component, a purely Ruby mental model, natural composition, excellent testability, and the kind of IDE support that makes refactoring feel safe.
Phlex and the Rails ecosystem
One concern developers often raise: does Phlex play nicely with the rest of Rails?
The answer is yes, deliberately so. Phlex was designed to work alongside ERB — you can render a Phlex component from an ERB template and vice versa. This means adoption can be incremental. You don’t rewrite your app; you introduce Phlex components one at a time, in the parts of the UI where the complexity justifies it.
Phlex also ships adapters for all major Rails helpers: link_to, image_tag, form_with, route helpers, content_for, and more. Turbo and Stimulus work exactly as you’d expect — Phlex just generates HTML, and Turbo/Stimulus don’t care where the HTML came from.
In this tutorial series, we’ll use Phlex exclusively from Module 4 onwards — no ERB views at all. This is the “all in” approach. It’s not the only valid approach, but it’s the best way to really understand what Phlex makes possible.
The mental model to carry forward
Before we write a single line of Phlex, internalise this:
A Phlex component is a Ruby object that knows how to render itself as HTML.
That’s it. Everything else — tags, attributes, composition, testing, kits, layouts — is a consequence of taking that idea seriously.
In the next module, we’ll start building that intuition with the Phlex HTML DSL, before touching Rails at all.
Module 1 summary
- ERB partials work well for simple, stable fragments. They break down when you need parameterised, reusable, testable UI.
- The four-way tension between partials, helpers, layouts, and instance variables creates implicit coupling that resists refactoring and testing.
- ViewComponent solves the interface and testing problems but keeps ERB as the template language.
- Phlex solves all of them: one file, explicit Ruby interface, natural composition, trivial testing.
- Phlex is fully compatible with Rails — adoption can be incremental, and all Rails helpers are supported.
- The core mental model: a component is a Ruby object that renders itself as HTML.