Phlex.Ex

Hex.pmHex.pmTest 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

Installation

Add phlex to your list of dependencies in mix.exs:

def deps do
  [
    {:phlex, "~> 0.1.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

Thanks

This project was inspired by and references several excellent projects:

License

The library is available as open source under the terms of the MIT License.