Elixir Plushie SDK

Build native desktop apps in Elixir. Pre-1.0

Write your entire application in Elixir (state, events, UI) and get native windows on Linux, macOS, and Windows. Available on Hex as :plushie. The renderer is built on Iced and ships as a precompiled binary, no Rust toolchain required.

SDKs are also available for Gleam, Python, Ruby, and TypeScript.

Quick start

defmodule Counter do
  use Plushie.App
  alias Plushie.Event.WidgetEvent
  import Plushie.UI

  def init(_opts), do: %{count: 0}

  def update(model, %WidgetEvent{type: :click, id: "inc"}),
    do: %{model | count: model.count + 1}

  def update(model, %WidgetEvent{type: :click, id: "dec"}),
    do: %{model | count: model.count - 1}

  def update(model, _event), do: model

  def view(model) do
    window "main", title: "Counter" do
      column padding: 16, spacing: 8 do
        text("count", "Count: #{model.count}")
        row spacing: 8 do
          button("inc", "+")
          button("dec", "-")
        end
      end
    end
  end
end

Download and run the counter example:

git clone https://github.com/plushie-ui/plushie-elixir.git
cd plushie-elixir

mix deps.get
mix plushie.download
mix plushie.gui Counter

The repo includes several other examples you can try. Edit them while the GUI is running and see changes instantly. For multi-file projects (custom widgets, collaborative transports, real project scaffolding), see the plushie-demos repo.

To add Plushie to your own project, see the getting started guide, or browse the docs for all guides and references.

How it works

Your Elixir application and the renderer run as two OS processes that exchange messages. Think of it like talking to a database, except the database is a GPU-accelerated GUI toolkit. The SDK builds UI trees and handles events; the renderer draws native windows and captures input.

The SDK diffs each new tree against the previous one and sends only the changes. If the renderer crashes, Plushie restarts it and re-syncs your state. If your code raises, the SDK reverts to the last good state. Neither process can take the other down.

The same protocol works over a local pipe, an SSH connection, or any bidirectional byte stream. Your code doesn't need to change.

Features

Testing and automation

Tests run through the real renderer binary, not mocks. Interact like a user: click, type, find elements, assert on text. All backends support concurrent test execution to keep your suite fast as it grows. Three interchangeable backends:

defmodule TodoTest do
  use Plushie.Test.Case, app: Todo

  test "add and complete a todo" do
    type_text("#new_todo", "Buy milk")
    submit("#new_todo")

    assert_text "#todo_count", "1 item"
    assert_exists "#todo:1"

    toggle("#todo:1")
    click("#filter_completed")

    assert_text "#todo_count", "0 items"
    assert_not_exists "#todo:1"
  end
end

The same interaction API is available outside ExUnit via Plushie.Automation.Session. Attach to any running app and drive it programmatically. Agent-friendly by design.

Status

Pre-1.0. The core works (built-in widgets, event system, themes, multi-window, testing framework, accessibility) but the API is still evolving. Pin to an exact version and read the CHANGELOG when upgrading.

License

MIT