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
endmix toddy.gui CounterOr 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
- 37 built-in widget types -- buttons, text inputs, sliders, tables, markdown, canvas, and more. Easy to build your own. Layout guide
- 22 built-in themes -- light, dark, dracula, nord, solarized, gruvbox, catppuccin, tokyo night, kanagawa, and more. Custom palettes and per-widget style overrides. Theming guide
- Multi-window -- declare window nodes in your widget tree; the framework opens, closes, and manages them automatically. App behaviour guide
- Platform effects -- native file dialogs, clipboard, OS notifications. Effects guide
- Accessibility -- screen reader support via accesskit on all platforms. Accessibility guide
- Live reload -- edit code, see changes instantly. Enabled by default in dev mode.
- Extensions -- multiple paths to custom widgets:
- Compose existing widgets into higher-level components with pure Elixir. No Rust, no binary rebuild.
- Draw on the canvas with shape primitives for charts, gauges, diagrams, and other custom 2D rendering.
- Native -- implement
WidgetExtensionin Rust for full control over rendering, state, and event handling. - Extensions guide
Testing
Toddy ships a test framework with three interchangeable backends. Write your tests once, run them at whatever fidelity you need:
- Mocked -- millisecond tests, no display server. Uses a shared mock process for fast logic and interaction testing.
- Headless -- real rendering via tiny-skia, no display server needed. Supports screenshots for pixel regression in CI.
- Windowed -- real windows with GPU rendering. Platform effects, real input, the works.
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
endSee 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:
- Pin to an exact version and read the CHANGELOG when upgrading.
- Mix releases are not yet supported.
-
The extension macro DSL (
Toddy.Extension) is the least stable part of the API.
Documentation
Guides are in docs/ and will be on
hexdocs once published:
- Getting started -- setup, first app, mix tasks, dev mode
- Tutorial -- build a todo app step by step
- App behaviour -- the Elixir API contract, multi-window
- Layout -- length, padding, alignment, spacing
- Events -- full event taxonomy
- Commands and subscriptions -- async work, timers, widget ops
- Effects -- native platform features
- Theming -- themes, custom palettes, styling
- Composition patterns -- tabs, sidebars, modals, cards, state helpers
- Scoped IDs -- hierarchical ID namespacing
- Testing -- three-backend test framework and pixel regression
- Accessibility -- accesskit integration, a11y props
- Extensions -- custom widgets, publishing packages
Development
mix preflight # run all CI checks locallyMirrors 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:
- Linux (Debian/Ubuntu):
sudo apt-get install libxkbcommon-dev libwayland-dev libx11-dev cmake fontconfig pkg-config - Linux (Arch):
sudo pacman -S libxkbcommon wayland libx11 cmake fontconfig pkgconf - macOS:
xcode-select --install - Windows:Visual Studio Build Tools with "Desktop development with C++"
Links
| Elixir SDK | github.com/toddy-ui/toddy-elixir |
| Rust binary | github.com/toddy-ui/toddy |
| Rust crate | crates.io/crates/toddy |
License
MIT