Hex.pmDocs

Status: Early development. Not yet ready for production use.

Dala

Dala is a native mobile framework for Elixir powered by the BEAM VM.

It brings OTP, lightweight processes, fault tolerance, and the actor model to iOS and Android development while using a Rust-powered native runtime for rendering and platform integration.

Unlike WebView-based frameworks, Dala focuses on native execution, concurrent application architecture, and local-first intelligent applications.

Why Dala?

Modern mobile applications are becoming increasingly complex:

These problems look more like distributed systems than traditional frontend applications.

Dala uses the strengths of the BEAM ecosystem to solve them naturally.

Features

Architecture

Elixir/Erlang
BEAM VM
Rust Native Runtime
iOS / Android

Dala keeps the BEAM runtime as the core execution engine while using Rust for performance-critical systems such as rendering, layout, native APIs, and ML integrations.

Vision

Dala is not trying to be another web wrapper for mobile apps.

The goal is to build an OTP-native runtime for modern mobile applications — especially apps that require:

Status

Dala is experimental and evolving rapidly.

Installation

Add to mix.exs:

def deps do
[{:dala, "~> 0.1.0"}]
end

The dala_new package (separate) provides project generation, deployment tooling, and will import dala_dev which is a live dashboard. Install it as a Mix archive:

mix archive.install hex dala_new
defmodule MyApp.CounterScreen do
use Dala.Spark.Dsl
attributes do
attribute :count, :integer, default: 0
end
screen name: :counter do
column do
gap :space_sm
text "Count: @count", text_size: :xl
button "Increment", on_tap: :increment
end
end
def handle_event(:increment, _params, socket) do
{:noreply, Dala.Socket.assign(socket, :count, socket.assigns.count + 1)}
end
end

A screen (Traditional - Alternative)

defmodule MyApp.CounterScreen do
use Dala.Screen
def mount(_params, _session, socket) do
{:ok, Dala.Socket.assign(socket, :count, 0)}
end
def render(assigns) do
%{
type: :column,
props: %{padding: :space_md, gap: :space_md, background: :background},
children: [
%{type: :text, props: %{text: "Count: #{assigns.count}", text_size: :xl, text_color: :on_background}, children: []},
%{type: :button, props: %{text: "Increment", on_tap: {self(), :increment}}, children: []}
]
}
end
def handle_event("tap", %{"tag" => "increment"}, socket) do
{:noreply, Dala.Socket.assign(socket, :count, socket.assigns.count + 1)}
end
end

App entry point

defmodule MyApp do
use Dala.App, theme: Dala.Theme.Obsidian
def navigation(_platform) do
screens([MyApp.CounterScreen])
stack(:home, root: MyApp.CounterScreen)
end
def on_start do
{:ok, _pid} = Dala.Screen.start_root(MyApp.CounterScreen)
cookie = Dala.Connectivity.Dist.cookie_from_env("MY_APP_DIST_COOKIE", "my_app")
Dala.Connectivity.Dist.ensure_started(node: :"my_app@127.0.0.1", cookie: cookie)
end
end

Navigation

# Push a new screen
Dala.Socket.push_screen(socket, MyApp.DetailScreen, %{id: 42})
# Pop back
Dala.Socket.pop_screen(socket)
# Tab bar layout
tab_bar([
stack(:home, root: MyApp.HomeScreen, title: "Home"),
stack(:profile, root: MyApp.ProfileScreen, title: "Profile")
])

Theming

# Named theme
use Dala.App, theme: Dala.Theme.Obsidian
# Override individual tokens
use Dala.App, theme: {Dala.Theme.Obsidian, primary: :rose_500}
# From scratch
use Dala.App, theme: [primary: :emerald_500, background: :gray_950]
# Runtime switch (accessibility, user preference)
Dala.Theme.set(Dala.Theme.Citrus)

Built-in themes: Dala.Theme.Obsidian (dark violet), Dala.Theme.Citrus (warm charcoal + lime), Dala.Theme.Birch (warm parchment).

Device APIs

All async — call the function, handle the result in handle_info/2:

# Haptic feedback (synchronous — no handle_info needed)
Dala.Hardware.Haptic.trigger(socket, :success)
# Camera
Dala.Media.Camera.capture_photo(socket)
def handle_info({:camera, :photo, %{path: path}}, socket), do: ...
# Location
Dala.Platform.Location.start(socket, accuracy: :high)
def handle_info({:location, %{lat: lat, lon: lon}}, socket), do: ...
# Push notifications
Dala.Platform.Notify.register_push(socket)
def handle_info({:push_token, :ios, token}, socket), do: ...

Also: Dala.Platform.Clipboard, Dala.Platform.Share, Dala.Media.Photos, Dala.Storage.Files, Dala.Media.Audio, Dala.Ui.Sensor.Motion, Dala.Hardware.Biometric, Dala.Hardware.Scanner, Dala.Hardware.NFC, Dala.Ui.Scan, Dala.Permissions.

Additional APIs: Dala.Hardware.Bluetooth (BLE), Dala.Connectivity.Wifi, Dala.Wakelock, Dala.Storage.Storage, Dala.Storage.Blob, Dala.Platform.Settings, Dala.Platform.State, Dala.Platform.Linking, Dala.Platform.Background, Dala.Ui.Feedback.Alert, Dala.Ui.Embedded.Webview.

Live development

mix dala.connect # tunnel + connect IEx to running device
nl(MyApp.SomeScreen) # hot-push new bytecode, no restart
# In IEx:
Dala.Test.screen(:"my_app_ios@127.0.0.1") #=> MyApp.CounterScreen
Dala.Test.assigns(:"my_app_ios@127.0.0.1") #=> %{count: 3, ...}
Dala.Test.tap(:"my_app_ios@127.0.0.1", :increment)

Testing

test "increments count" do
{:ok, pid} = Dala.Screen.start_link(MyApp.CounterScreen, %{})
:ok = Dala.Screen.Screen.dispatch(pid, "tap", %{"tag" => "increment"})
assert Dala.Screen.Screen.get_socket(pid).assigns.count == 1
end
PackagePurpose
dala_devDev tooling: mix dala.new, mix dala.deploy, mix dala.connect, live dashboard
dala_newGenerator project tool
dala_runtimeAOT compiler & runtime for BEAM, fix limitations of JIT

Documentation

Full documentation at hexdocs.pm/dala, including:

License

MPL-2.0