Emerge

HexHexDocsCILicense

Write native GUI directly from Elixir using declarative API.

Installation

Add :emerge to your dependencies:

defp deps do
  [
    {:emerge, "~> 0.1.0"}
  ]
end

Then run:

mix deps.get

Quick example

defmodule MyApp.View.Counter do
  use Emerge
  use Solve.Lookup

  @impl Viewport
  def mount(opts), do: {:ok, Keyword.merge([title: "Counter"], opts)}

  @impl Viewport
  def render() do
    counter = solve(MyApp.State, :counter)

    row(
      [
        Background.color(color(:slate, 800)),
        Font.color(color(:white)),
        spacing(12),
        padding(12)
      ],
      [
        my_button([Event.on_press(event(counter, :increment))], text("+")),
        el([padding(10)], text("Count: #{counter.count}")),
        my_button([Event.on_press(event(counter, :decrement))], text("-"))
      ]
    )
  end

  # Reusable "component" is just plain elixir function
  def my_button(attrs, content) do
    Input.button(
      attrs ++ [
        padding(10),
        Background.color(color(:sky, 500)),
        Border.rounded(8)
      ],
      content
    )
  end

  @impl Solve.Lookup
  def handle_solve_updated(_updated, state), do: {:ok, Viewport.rerender(state)}
end
Rendered counter example

Easy reuse

Reuse in Emerge is just Elixir. Build data, map over it, and extract helpers that return UI trees.

defmodule MyApp.UI do
  use Emerge.UI

  def overview do
    column(
      [
        width(fill()),
        padding(20),
        spacing(12),
        Background.color(color(:slate, 900)),
        Border.rounded(12)
      ],
      [
        el([Font.size(22), Font.color(color(:white))], text("Overview")),
        row([spacing(12)], Enum.map(summary_stats(), &stat_card/1))
      ]
    )
  end

  defp summary_stats do
    [
      {"Open", "12"},
      {"Closed", "34"},
      {"Owners", "5"}
    ]
  end

  defp stat_card({label, value}) do
    el(
      [
        width(fill()),
        padding(12),
        Background.color(color(:slate, 800)),
        Border.rounded(8)
      ],
      column([spacing(4)], [
        el([Font.color(color(:slate, 300))], text(label)),
        el([Font.size(20), Font.color(color(:white))], text(value))
      ])
    )
  end
end
Rendered easy reuse example

There is no separate component model to learn. If a function returns UI, you can compose it like any other Elixir function.

State management

Emerge is designed with Solve as a state management solution to keep complex UI apps sane. It keeps shared application state and rerender coordination outside the viewport process while Emerge stays focused on rendering.

Emerge does not depend on Solve. You can use another state management approach if it fits your app better.

Try it out

Try the standalone demo app in emerge_demo.

Features

Backends

For runtime backend selection and multi-backend setup, see Set up a viewport.

Requirements

Documentation

API reference and tutorials are published on HexDocs.

Key guides:

Run mix docs to build the full docs locally.

Attribution

Emerge's UI API is heavily inspired by elm-ui by Matthew Griffith.

Third-Party Assets

Bundled third-party asset notices are documented in THIRD_PARTY_ASSETS.md. Package/runtime-relevant notices are summarized in NOTICE.

Packaged/runtime-relevant asset groups:

If you redistribute Emerge inside an application or firmware image, include the applicable notice files.