It’s time for a new way to compose views in Ruby, using... ✨Ruby.✨ We don’t need another templating language. With Phlex, you can write small, reusable view components using simple Ruby Objects and templates composed of Ruby blocks.
Warning
Phlex is still in early development and should not be used in production yet.
Phlex is licenced under MIT and is being developed in the open by Joel Drapper. The plan is to release a stable 1.0
version in the next few months.
If you want to help out, you can contribute by opening an issue or PR. You can also book a pairing session with me. If you work for a company that would benefit from Phlex, I accept sponsorships through GitHub.
You can define a component by subclassing Phlex::Component
and defining a template
method where you can build HTML with simple block syntax. Each HTML tag is a method that accepts keyword arguments for its attributes. Text content can be passed as the first positional argument or alternatively, you can open a block to create nested HTML tags.
class NavComponent < Phlex::Component
def template
nav id: "main_nav" do
ul do
li { a "Home", href: "/" }
li { a "About", href: "/about" }
li { a "Contact", href: "/contact" }
end
end
end
end
It’s also possible to add classes to a tag by chaining methods calls like CSS selectors.
class CardComponent
def template(&)
div.shadow.rounded.p_5(&)
end
end
Components can accept arguments by defining an initializer.
class MessageComponent < Phlex::Component
def initialize(name:)
@name = name
end
def template
h1 "Hello #{@name}"
end
end
The default initialiser inherited from Phlex::Component
sets each keyword argument to an instance variable, so the above could be re-written to call super
instead.
class MessageComponent < Phlex::Component
def initialize(name:)
super
end
def template
h1 "Hello #{@name}"
end
end
Components, like tags, can accept nested contents as a block given to template
, which they can then yield or pass to another tag.
Here the content is passed to a div
tag:
class CardComponent < Phlex::Component
def template(&)
div.rounded.drop_shadow.p_5(&)
end
end
Here the content is yielded inside a div
tag right after an h1
tag.
class CardComponent < Phlex::Component
def initialize(title:)
super
end
def template(&)
div.rounded.drop_shadow.p_5 do
h1 @title
content(&)
end
end
end
Components can be nested inside other components too.
class NavComponent < Phlex::Component
def initialize(links:)
super
end
def template
nav do
ul do
@links.each do |label, link|
component Nav::ItemComponent, label:, link:
end
end
end
end
end
class Nav::ItemComponent < Phlex::Component
def initialize(label:, link:)
super
end
def template
li { a label, href: link }
end
end
Instance variables and methods from the components are accessible deep inside nested components and tag blocks because blocks capture their context for execution.
class ArticlesComponent < Phlex::Component
def initialize(articles:)
super
end
def template
component CardComponent, title: "Articles" do
@articles.each do |article|
h2 @article.title
text @article.content
end
end
end
end
Becuase components accept blocks, it’s really easy to define advanced components with their own DSLs. Take, for instance, this table fabricator component that lets you define rows / columns with headers using blocks.
component Table::Fabricator, @articles, layout: :column do |t|
t.data "Title" { a _1.title, href: article_path(_1) }
t.text_data("Author", &:author)
t.text_data("Published", &:published_at)
end
This badge component takes a color argument and uses Ecase to build a list of Tailwind classes.
class BadgeComponent < Phlex::Component
VALID_COLORS = [:sky, :teal, :rose, :slate, :orange]
def initialize(color:)
super
end
def template(&)
span(class:, &)
end
private
def class
[:px_1, :py_1, :rounded_full, :text_sm] + colors
end
def colors
ecase @color, VALID_COLORS do
on(:sky) { [:bg_sky_200, :ring_sky_300, :text_sky_900] }
on(:teal) { [:bg_teal_100, :ring_teal_200, :text_teal_800] }
on(:rose) { [:bg_rose_100, :ring_rose_200, :text_rose_900] }
on(:slate) { [:bg_slate_200, :ring_slate_300, :text_slate_800] }
on(:orange) { [:bg_orange_200, :ring_orange_300, :text_orange_900] }
end
end
end
Add this line to your application's Gemfile:
gem 'phlex'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install phlex
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Phlex project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Logo design sponsored by Logology.