It’s time for a new way to compose views in Ruby, using...
Warning
Phlex is still in early development and should not be used in production yet.
The Road to 1.0
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.
Usage
Basic Component
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
Emmet-style CSS classes
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
Component Attributes
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
Content
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
Nested components
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
Advanced components with DSLs
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
Other examples
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
Installation
Add this line to your application's Gemfile:
gem 'phlex'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install phlex
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Phlex project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Credits
Logo design sponsored by Logology.