Hex.pm Hex.pm Test Status

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_capsule package
  • 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.3.0"}
  ]
end

For 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
end

2. 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
end

3. 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
end

Then 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
end

Phoenix 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
end

StyleCapsule 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
end

Example 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.server

Then 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 dialyzer

Contributing

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.