toddy

Build native desktop apps in Elixir. Pre-1.0

Toddy is a desktop GUI framework that allows you to write your entire application in Elixir -- state, events, UI -- and get native windows on Linux, macOS, and Windows. Rendering is powered by iced, a cross-platform GUI library for Rust, which toddy drives as a precompiled binary behind the scenes.

defmodule Counter do
  use Toddy.App
  alias Toddy.Event.Widget
  import Toddy.UI

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

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

  def update(model, %Widget{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
mix toddy.gui Counter

Or from IEx:

iex> Toddy.start_link(Counter)

This is one of 8 examples included in the repo, from a minimal counter to a full widget catalog. Edit them while the GUI is running and see changes instantly.

Getting started

Add toddy to your dependencies:

# mix.exs
{:toddy, "== 0.3.0"}

Then:

mix deps.get
mix toddy.download                    # download precompiled binary
mix toddy.gui Counter                 # run the counter example

Pin to an exact version (==, not ~>) and read the CHANGELOG carefully when upgrading.

The precompiled binary requires no Rust toolchain. To build from source instead, install rustup and run mix toddy.build. See the getting started guide for the full walkthrough.

Features

Testing

Toddy ships a test framework with three interchangeable backends. Write your tests once, run them at whatever fidelity you need:

defmodule TodoTest do
  use Toddy.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

See the testing guide for the full API, backend details, and CI configuration.

How it works

Under the hood, a Rust binary built on iced handles rendering and platform integration. Your Elixir app sends widget trees to the binary over stdin; the binary renders native windows and sends user events back over stdout.

You don't need Rust to use toddy. The binary is a precompiled dependency, similar to how your app talks to a database without you writing C. If you ever need custom native rendering, the extension system lets you write Rust for just those parts.

Status

Pre-1.0. The core works -- 37 widget types, event system, 22 themes, multi-window, testing framework, accessibility -- but the API is still evolving:

Documentation

Guides are in docs/ and will be on hexdocs once published:

Development

mix preflight                         # run all CI checks locally

Mirrors CI and stops on first failure: format, compile (warnings as errors), credo, test, dialyzer.

System requirements

The precompiled binary (mix toddy.download) has no additional dependencies. To build from source, install a Rust toolchain via rustup and the platform-specific libraries:

Links

Elixir SDK github.com/toddy-ui/toddy-elixir
Rust binary github.com/toddy-ui/toddy
Rust crate crates.io/crates/toddy

License

MIT