Phlex.Ex
Object-oriented web views in Elixir. Build HTML, SVG, and other markup views using Elixir's functional programming paradigm with component-based architecture.
Migrating from the Ruby version? This Elixir implementation follows the same philosophy as Phlex but embraces Elixir's functional nature with explicit state passing.
Features
- Component-based architecture - Build reusable HTML and SVG components
- Type-safe - Dialyzer support for type checking
- Phoenix integration - Works seamlessly with Phoenix and Phoenix LiveView
- Standalone usage - Can be used without Phoenix in plain Elixir applications
- StyleCapsule integration - Optional CSS scoping support via
style_capsulepackage - Modern Elixir - Uses Elixir 1.18+ features and patterns
Installation
Add phlex to your list of dependencies in mix.exs:
def deps do
[
{:phlex, "~> 0.1.0"}
]
endFor Phoenix integration, also ensure you have:
{:phoenix, "~> 1.7"},
{:phoenix_live_view, "~> 0.20"}
For CSS scoping support, consider adding style_capsule:
{:style_capsule, "~> 0.7.0"}Quick Start
1. Create a Component
defmodule MyAppWeb.Components.Card do
use Phlex.HTML
def view_template(assigns, state) do
div(state, [class: "card"], fn state ->
h1(state, [class: "title"], Map.get(assigns, :title, "Card"))
p(state, [], Map.get(assigns, :content, ""))
end)
end
end2. Use in Phoenix LiveView
defmodule MyAppWeb.PageLive do
use MyAppWeb, :live_view
def render(assigns) do
Phlex.Phoenix.to_rendered(
MyAppWeb.Components.Card.render(%{title: "Hello", content: "World"})
)
end
end3. Optional: Add CSS Scoping with StyleCapsule
For component-scoped CSS, integrate with style_capsule:
defmodule MyAppWeb.Components.Card do
use Phlex.HTML
@component_styles """
.card { padding: 1rem; border: 1px solid #ccc; }
.title { font-size: 1.5rem; font-weight: bold; }
"""
def view_template(assigns, state) do
# Register styles
capsule_id = Phlex.StyleCapsule.capsule_id(__MODULE__)
StyleCapsule.Phoenix.register_inline(@component_styles, capsule_id)
# Add capsule attribute
attrs = Phlex.StyleCapsule.add_capsule_attr([class: "card"], __MODULE__)
div(state, attrs, fn state ->
h1(state, [class: "title"], Map.get(assigns, :title, "Card"))
end)
end
endThen render styles in your layout:
<body>
<%= @inner_content %>
<%= raw StyleCapsule.Phoenix.render_all_runtime_styles() %>
</body>Usage
Phlex components use explicit state passing, following Elixir's functional programming style. The view_template/2 function receives assigns and a state struct, then returns the updated state.
Basic HTML Component
defmodule MyApp.Components.Card do
use Phlex.HTML
def view_template(assigns, state) do
div(state, [class: "card"], fn state ->
h1(state, [class: "title"], Map.get(assigns, :title, "Default"))
p(state, [], Map.get(assigns, :content, ""))
end)
end
end
MyApp.Components.Card.render(%{title: "Hello", content: "World"})
# => "<div class=\"card\"><h1 class=\"title\">Hello</h1><p>World</p></div>"SVG Component
defmodule MyApp.Components.Icon do
use Phlex.SVG
def view_template(_assigns, state) do
svg(state, [viewBox: "0 0 24 24"], fn state ->
circle(state, [cx: "12", cy: "12", r: "10"])
end)
end
endPhoenix LiveView Integration
When using Phlex components in LiveView, convert the HTML string to a Phoenix.LiveView.Rendered struct:
defmodule MyAppWeb.PageLive do
use MyAppWeb, :live_view
def render(assigns) do
Phlex.Phoenix.to_rendered(
MyAppWeb.Components.Card.render(assigns)
)
end
endStyleCapsule Integration
For component-scoped CSS, Phlex provides helpers through Phlex.StyleCapsule. Register your styles and add capsule attributes to scope your CSS automatically:
defmodule MyAppWeb.Components.Card do
use Phlex.HTML
@component_styles """
.card { padding: 1rem; }
.title { font-weight: bold; }
"""
def view_template(assigns, state) do
# Register styles with StyleCapsule
capsule_id = Phlex.StyleCapsule.capsule_id(__MODULE__)
StyleCapsule.Phoenix.register_inline(@component_styles, capsule_id, namespace: :app)
# Add data-capsule attribute
attrs = Phlex.StyleCapsule.add_capsule_attr([class: "card"], __MODULE__)
div(state, attrs, fn state ->
h1(state, [class: "title"], Map.get(assigns, :title, "Card"))
end)
end
end
Alternatively, use StyleCapsule.PhlexComponent for automatic integration:
defmodule MyAppWeb.Components.Card do
use StyleCapsule.PhlexComponent
@component_styles """
.card { padding: 1rem; }
.title { font-weight: bold; }
"""
defp render_template(assigns, attrs, state) do
div(state, attrs, fn state ->
h1(state, [class: "title"], Map.get(assigns, :title, "Card"))
end)
end
endExample Applications
A complete Phoenix example application is available in examples/phoenix_demo/. It demonstrates Phlex components, Phoenix LiveView integration, and StyleCapsule CSS scoping.
To run the example:
cd examples/phoenix_demo
mix deps.get
mix phx.serverThen visit http://localhost:4000 to see Phlex in action.
Development
# Install dependencies
mix deps.get
# Run tests
mix test
# Run all quality checks
mix quality
# Format code
mix format
# Run code analysis
mix credo --strict
# Run type checking
mix dialyzerContributing
Bug reports and pull requests are welcome on GitHub at https://github.com/amkisko/phlex.ex
See CONTRIBUTING.md for guidelines.
Requirements
- Elixir >= 1.18
- Phoenix >= 1.7 (optional, for Phoenix integration)
- Phoenix LiveView >= 0.20 (optional, for LiveView integration)
- StyleCapsule >= 0.7.0 (optional, for CSS scoping support)
Thanks
This project was inspired by and references several excellent projects:
- Phlex (GitHub) - The original Ruby gem for building object-oriented web views that inspired this Elixir implementation
- Phoenix Framework - The web framework that makes building real-time applications in Elixir a joy
- Surface (GitHub) - A component-based library for Phoenix that provided architectural inspiration
License
The library is available as open source under the terms of the MIT License.