| name | jr-rails-phlex |
| description | Write Phlex views and components for Rails: class hierarchy, slots, helpers, custom elements, scaffold generator. Use when building UI with phlex-rails. |
Phlex Views & Components
Components are Ruby objects — no template language, no DSL. Views replace ERB
templates (one per controller action). Components are reusable UI building blocks.
Core Workflow
- Use the scaffold generator — custom
PhlexControllerGenerator produces
Phlex views instead of ERB. See reference/coding-phlex.md § Scaffolding for the full generator code and all 5 templates.
- Implementation order — models → controllers → views/components → tests.
- Forms default to ERB partials — Rails form builders are ergonomic there.
Phlex form helpers exist for simple cases.
Class Hierarchy
Components::Base < Phlex::HTML (include Components, route helpers, dev comments)
├── Views::Base (+ Debug, ContentFor, caching)
├── Components::Layout (+ Phlex::Rails::Layout)
└── Components::PageHeader, TitleBar, List, GridCell, ...
class Components::Base < Phlex::HTML
include Components
include Phlex::Rails::Helpers::Routes
if Rails.env.development?
def before_template
comment { "Before #{self.class.name}" }
super
end
end
end
class Views::Base < Components::Base
include Phlex::Rails::Helpers::Debug
def cache_store = Rails.cache
end
Short-form Component Calls
include Components on the base class enables method-style rendering:
render Components::PageHeader.new(title: "Labels")
PageHeader(title: "Labels")
Component Slots via Methods
Public methods on components yield named content areas:
class Components::TitleBar < Components::Base
def view_template(&) = div(class: "title-bar", &)
def leading_action(&) = div(class: "leading-action", &)
def title(&) = h1(&)
def actions(&) = div(class: "actions", &)
end
render Components::TitleBar.new do |bar|
bar.leading_action { link_to("Back", labels_path) }
bar.title { "My Page" }
bar.actions { button_to("Delete", @label, method: :delete) }
end
Helper Includes
Include only the Phlex::Rails::Helpers::* each view needs — never on Base:
class Views::Labels::Show < Views::Base
include Phlex::Rails::Helpers::ContentFor
include Phlex::Rails::Helpers::DOMID
include Phlex::Rails::Helpers::ButtonTo
include Phlex::Rails::Helpers::LinkTo
include Phlex::Rails::Helpers::TurboFrameTag
end
Common helpers: Routes, ContentFor, DOMID, ButtonTo, LinkTo,
TurboFrameTag, TurboStreamFrom, ImageTag, ClassNames, Request,
Notice, Debug, Sanitize, StripTags.
Content Areas
Layout yields named areas; views populate via content_for:
content_for :title, "Labels"
content_for :main_header do
render Components::PageHeader.new do |header|
header.title_bar { |bar| bar.title { "Labels" } }
end
end
content_for :floating_action do
render Components::FloatingActionMenu.new
end
Standard areas: :title, :main_header, :floating_action, :head.
Controller Rendering
Controllers render Phlex views directly, passing data via new:
class LabelsController < ApplicationController
def index
@pagy, @labels = pagy(Label.all)
render Views::Labels::Index.new(@labels, @pagy)
end
def show
render Views::Labels::Show.new(@label)
end
def create
@label = Label.new(label_params)
if @label.save
redirect_to @label, notice: "Label was successfully created."
else
render Views::Labels::New.new(@label), status: :unprocessable_entity
end
end
end
Custom Element Wrappers
Use register_element for web component custom elements (Web Awesome, Shoelace, etc.):
module Components::MyLibrary
class MyButton < Phlex::HTML
register_element :my_button
def initialize(variant: "neutral", size: "medium", **attributes)
@attributes = attributes.with_defaults(variant: variant, size: size)
end
def view_template(&) = my_button(**@attributes, &)
end
end
Use phlex_custom_element_generator to auto-generate wrappers from custom element manifests.
Register Helpers
For Rails helpers that output HTML or return values:
register_output_helper :vite_client_tag
register_output_helper :vite_javascript_tag
register_output_helper :column_chart
register_value_helper :alert
ERB Partials for Forms
Phlex views render ERB form partials seamlessly:
section { render partial("form", label: @label) }
Fragment Caching
def view_template
cache("labels/#{@label.id}/card") {
}
end
Multiple Layouts
class Components::Layout < Components::Base
include Phlex::Rails::Layout
end
class Components::MarketingLayout < Components::Base
include Phlex::Rails::Layout
end
Set in controllers: layout -> { Components::Layout }
Frontend Integration
Stimulus
div(data: {
controller: "faceted-search",
action: "input->faceted-search#perform:prevent",
faceted_search_url_value: labels_path
}) { ... }
Turbo Frames (Lazy Loading)
turbo_frame_tag(album, src: album_path(album), loading: :lazy) {
render Components::Spinner.new
}
Turbo Streams & Morphing
turbo_stream_from([@budget, :items])
meta name: "turbo-refresh-method", content: "morph"
meta name: "turbo-refresh-scroll", content: "preserve"
Pagy
class Views::Labels::Index < Views::Base
include Pagy::Frontend
def initialize(labels, pagy)
@labels = labels
@pagy = pagy
end
def view_template
raw safe(pagy_nav(@pagy))
end
end
Heuristics
- Component vs View: reusable UI = component; page-level (one per action) = view
- When to use ERB: forms with Rails form builder, complex form logic
- Naming:
Components::PageHeader, Views::Labels::Index
- One view per controller action; views receive data via
initialize
- Composition over inheritance — slot methods, not deep class hierarchies
- Data flow: controller → view (
initialize) → components (render)
Anti-patterns
- God components — split into smaller, focused components
- Business logic in views/components — belongs in models
- Over-including helpers on Base — include per-view
- Reimplementing form builders — use ERB partials for forms
- Passing request context into components — components receive data, not request objects
Preferred Stack
| Concern | Choice |
|---|
| Components | phlex-rails 2.x |
| Bundling | vite_rails / importmap / esbuild |
| Pagination | Pagy |
| Custom elements | library of choice + phlex_custom_element_generator |
| Scaffolding | Custom PhlexControllerGenerator |
| Frontend | turbo-rails + stimulus-rails |
| Feature flags | Flipper (inline: if Flipper.enabled?(:feature)) |
Deep Reference Files
Read these on demand when the task requires deeper guidance:
For frontend patterns (Stimulus controllers, Turbo Frames/Streams), invoke
the relevant hwc-* skill alongside this one.