Skip to content
AimesSoftPublic

About

One Rust declaration, compiled into each platform's own native UI: SwiftUI, WinUI 3, GTK 4 and the Web. Built for AI: what an agent forgets fails to compile, and every button is an MCP tool. No JS engine, no embedded browser.

Topics

Resources

Contributing

Stars

5 stars

Watchers

1 watching

Forks

Repository files navigation

Bern logo

Bern

One Rust declaration, compiled into each platform's own native UI.

From the desktop to the phone, built for AI: SwiftUI, WinUI 3, GTK 4, Jetpack Compose and the Web.
No JS engine and no embedded browser on the desktop or the phone; every button is an MCP tool an AI can call.

macOS · SwiftUI iOS · SwiftUI Windows · WinUI 3 Linux · GTK 4 Android · Jetpack Compose Web · WebAssembly

AI native: MCP built in Memory about 40 MB No JS engine Rust 1.99 License: MIT Status: early preview

English · 简体中文 · 繁體中文 · 日本語

Why Bern · Built with Bern · Built for AI · Measured · Comparison · Quick start · Design document


The showcase on macOS with SwiftUI
macOS · SwiftUI
The showcase on Windows with WinUI 3
Windows · WinUI 3
The showcase on Linux with GTK 4 and libadwaita
Linux · GTK 4 + libadwaita
The showcase in a browser
Web · DOM + WebAssembly

The showcase on Android with Jetpack Compose
Android · Jetpack Compose (dark, following the system)

One controls_page declaration, five UI toolkits. On the desktop and on Android Bern draws no pixels of its own:
every switch, slider and sidebar is the platform's standard control, in the platform's own colors.

Why Bern

Why Bern: compiled into native UI, one Rust declaration generating each platform's own UI code with no JS engine at runtime; built for AI, where omissions are compile errors and every button is an MCP tool; too light to feel cross-platform, 39 MB, 0.4 s and 2.7 MB on macOS, 35 MB, 0.3 s and 4.2 MB on Windows, 189 KB on the web

Built with Bern

RustSend · Local file sharing

RustSend is an open-source local file sharing app built with Bern and compatible with LocalSend v2. Its UI declarations, state and transfer logic are written in Rust. Bern generates native SwiftUI interfaces for macOS and iOS, WinUI 3 for Windows, GTK 4 for Linux, and Jetpack Compose for Android.

It supports device discovery, file and text transfers, receive approval, and in-app completion notices, sharing the same application logic between compact desktop windows and phone layouts. In the local 1.0 macOS ARM64 Release build, the complete .app measures 8.09 MiB and the download .zip 3.80 MiB. See the measurement record for the build environment and methodology.

RustSend 1.0 Receive page on a physical Mac
macOS · SwiftUI
RustSend 1.0 Receive page on a physical Android device
Android · Jetpack Compose

Source code · Download RustSend · Screenshots and capture environments for all five platforms

Built for AI

Writing code: what the AI forgets, the compiler remembers

AI rarely gets interface code wrong; it leaves things out. A feature is added without an entry in the compact layout, a new string is written in one language only, a callback sidesteps the commands. Elsewhere these wait for a user to find them; in Bern they do not compile. The stricter the rules, the harder it is for an AI to hand over half a job:

Five things an AI forgets and what Bern does: a missing language is B102, a feature without an entry in some layout is B201, an entry not registered to a feature is B202, an entry only inside a conditional branch is B203, and arbitrary code on a button cannot be written at all

Errors are written for people and for agents alike: a code, a reason and a location, so an agent can fix them and build again on its own. Two real runs on the showcase: the first with an English translation deleted, the second, after restoring it, with the compact layout's "Change Clip" menu removed:

$ bern check
error[B102]: Missing translation
Missing language: en
Required languages: zh-Hans, en
  --> showcase-ui/src/pages.rs:13:31

$ bern check
error[B201]: Feature missing in adaptive layout
Contract: MediaFeature
Feature: Clip
compact: missing
  --> showcase-ui/src/media.rs:14:5

Every build also writes a full feature coverage report per target (features.json) that lists what is covered, exempt or only manually guaranteed.

Using the app: meaning, not pixels

In most frameworks, letting an AI operate a desktop app means screenshots, recognition, coordinates and synthetic clicks. A Bern app ships with an MCP server: every control has a stable name and role, the agent reads structure, its actions take the same code path as a user's click, and the app answers with what actually happened.

$ showcase ui inspect                 # the agent reads meaning, not pixels
[{ "target": "controls_page.sync", "role": "switch", "label": "Sync", "value": "false", "enabled": true, … }, …]

$ showcase ui set-bool --target controls_page.sync --value true

$ showcase window close --window settings#1
{ "outcome": "ConfirmationRequired", "message": "Do you want to save the display name?",
  "decisions": ["Save", "Discard", "Cancel"], "confirmation": "settings#1:c1", … }

$ showcase window confirm-close --window settings#1 --confirmation settings#1:c1 --decision Save
{ "outcome": "Rejected", "reason": "save_failed: invalid_name: a display name has 1 to 40 characters …" }

The same operations are MCP tools for agents (ui_inspect, ui_activate, window_close, …), and each business command is a tool too (for example showcase_set_display_name). When a button is disabled, blocked by a dialog or missing, the agent gets a clear error code rather than a click that does nothing.

Measured

Measured: macOS (SwiftUI) launches in 0.41 s with 39 MB of memory and a 2.7 MB bundle; Windows (WinUI 3) in 0.32 s with 35 MB and a 4.2 MB folder; the web app is 189 KB gzipped
How it was measured
  • Subject: the release showcase (6 pages, a main and a settings window, a video player), measured on 2026-10-11, median of at least 3 runs.
  • macOS: Apple M4, 16 GB, macOS 27.0.1. Memory is the physical footprint from footprint, the measure of Activity Monitor's Memory column.
  • Windows: Intel Core i5-13490F, 32 GB, Windows 11 25H2. Memory is the private working set, the measure of Task Manager's Memory column.
  • Launch time: from starting the process until the command line sees a visible native window, including engine start and the local control channel.
  • The Windows numbers are the WinUI 3 front end (measured with Windows App Runtime 1.8; builds now use 2.x). Without the runtime, the same program falls back to the system XAML: 0.54 s, 19 MB. Neither needs .NET or the VC++ runtime; the app folder holds the executable and a 400 KB bootstrapper DLL.

More

More: every action is a command, checked at compile time, your logic stays in Rust, localized from day one, dark mode with zero setup, declarative multi-window
And more: video, async queries, styles and motion, drawing, media app components, reading the files users hand you, modular apps, adaptive layouts…
  • Video: Video plays on AVFoundation, Windows Media Foundation, GStreamer, Media3 on Android or HTML media, with a command-driven control bar and players that outlive a page.
  • Async queries: Query subscriptions with loading, empty, failure and refresh states; only the latest answer is shown.
  • Adaptive layouts: layouts for compact, medium, expanded and large, chosen by the space a view actually gets; expressions read the window's size too, for margins that grow with it.
  • Typed navigation: sidebars, push navigation with typed routes, tabs, toolbars and menus.
  • Feature contracts: declare which features a component must cover in each layout; the compiler proves the coverage.
  • Bundled resources: files declared in Bern.toml, checked at build time and located by name at runtime.
  • Images and layout: Image shows bundled pictures, HDR ones (PQ and HLG PNGs, JPEGs with a gain map) in HDR where the display can, and an undeclared one fails to compile; Grid adds and drops columns with the width, Stack layers views with nine alignments, Progress shows a bar or indeterminate progress.
  • Style overrides and motion: controls keep the platform look by default; for one design everywhere, style! declares app-wide overrides, spring motion, appear and disappear transitions, staggered entrances and shared elements, and an override a target cannot show fails the build (on all five front ends).
  • Drawing and placing by fraction: Canvas draws shapes, data curves and check patterns in semantic colors, and Text can carry a highlight mark; Rail puts elements at fractions that come from state, for timelines, ticks on a progress bar or a fill that grows.
  • Media app components: a sidebar footer, toolbar search with results, an inspector, toasts, split buttons and popover buttons holding any content, buttons with content that reveal more on hover, shortcuts, picking and dropping files, scroll snapping, pinch and drag to set a value, on all five front ends.
  • Reading the files users hand you: for a file the user picked, dropped or opened with the app, the engine gets its name and bytes with MediaSource::file_name() and open(), the same for a local path, an Android content:// document or a page's blob: file.
  • Modular apps: an app is a shell and modules, each with its own protocol, engine, interface and translations, switchable at runtime; bern new --module adds one, so several people and AIs can work side by side (verified on macOS and Android).

How it works

flowchart LR
    subgraph app["Your application (Rust)"]
        P["Protocol<br/>types · state · commands"]
        U["UI declarations<br/>component + ui!"]
        E["Engine<br/>plain Rust"]
    end
    P --> C{{"bern build<br/>compiler + IR"}}
    U --> C
    C --> S["SwiftUI<br/>macOS · iOS"]
    C --> W["WinUI 3<br/>Windows"]
    C --> G["GTK 4<br/>Linux"]
    C --> A["Jetpack Compose<br/>Android"]
    C --> B["DOM + WASM<br/>Web"]
    E <--> K[["Command catalog"]]
    K <--> N["Native UI"]
    K <--> L["App CLI"]
    K <--> J["JSON channel"]
    K <--> M["MCP server"]
Loading
  1. Protocol – #[bern::protocol] declares value types, the read-only state and the command and query traits.
  2. Engine – implements those traits in ordinary Rust; it never sees a window, a control or a transport.
  3. Interface – #[component] functions whose body is a ui! declaration. The compiler reads them into a shared intermediate representation, and each target module generates native code from it. Adding a target never touches the parser, the IR or your business logic.

A taste of Bern

1 · Protocol: types, state and commands

#[bern::protocol(namespace = "counter", version = 1)]
pub mod counter {
    use bern::prelude::*;

    #[state]
    pub struct State {
        pub count: i64,
    }

    #[commands]
    pub trait Commands {
        /// Add to the count.
        fn add(&mut self, by: i64) -> Result<(), CommandError>;
    }
}

2 · Engine: plain Rust, no UI

use bern::CommandError;

pub struct Counter {
    state: counter::State,
}

impl Default for Counter {
    fn default() -> Self {
        Self { state: counter::State { count: 0 } }
    }
}

impl counter::Commands for Counter {
    fn add(&mut self, by: i64) -> Result<(), CommandError> {
        self.state.count += by;
        Ok(())
    }
}

impl counter::Engine for Counter {
    fn state(&self) -> &counter::State {
        &self.state
    }
}

3 · Interface: a ui! declaration

#[component]
pub fn counter_view(app: counter::Client) -> impl View {
    ui! {
        Form {
            Text(content: tr! {
                "zh-Hans" => "已计数 {n} 次",
                "en" => "Counted {n} times";
                n: app.count
            })
            Button(
                id: add,
                label: tr! { "zh-Hans" => "加一", "en" => "Add one" },
                prominence: prominent
            ) => app.add(1)
        }
    }
}

4 · Everyone drives the same engine

counter ui inspect
counter ui activate --target counter_view.add
counter counter add --by 5
counter-mcp   # the same catalog for AI agents

Bern.toml declares languages, theme, sources, bundled resources and targets. See the full showcase: a sidebar app with text styles, buttons, switches, sliders, text fields, an engine-backed search, list-to-detail navigation, video, and a settings window with tabs and a close confirmation. The planner is a modular app with style overrides, motion and Canvas. The gallery shows the media app components: a poster wall that enters in turn, a timeline placed by fraction, a volume popover, margins and layouts that follow the four sizes, and a dropped video whose name, format and size the engine reads from the file.

Platforms

Platforms: macOS, Windows, Linux, the web app, WebUI and Android are available, iOS and tvOS build simulator apps today; next are flutter + macos, iOS device packages and HarmonyOS

Implementation notes: macOS · iOS · tvOS · Windows · Linux · Web and WebUI · Android; the next platforms are in the adaptation specs.

Omitting the UI technology picks the platform default; an unavailable default fails loudly instead of silently switching to another stack.

Compared with other cross-platform frameworks

GPUI Dioxus Flutter React Native uni-app
Electron Tauri Avalonia Qt

Every framework answers the same question: how does one codebase become the interface on each platform? The answers differ, and so do the trade-offs. What follows is based on each project's official documentation; size and memory vary a lot with versions and settings, so the comparison stays structural. Bern's own numbers are in Measured.

Four routes: native UI code generated at build time (Bern), native views driven at runtime (React Native, uni-app x), an own rendering engine (Flutter, GPUI, Avalonia, Qt), web technology (Electron, Tauri, Dioxus, uni-app)

The first two routes give you system controls: look, accessibility, input methods and dark mode come from the system, at the cost of controls that differ a little between platforms. The other two draw the same interface everywhere, at the cost of keeping up with each platform's design and behavior yourself, or of shipping a browser.

Where the UI comes from and how much runtime ships with the app: Bern sits in the corner of system controls and the least runtime

The chart is a sketch by architecture, not a ranking: the horizontal axis is whether UI elements are the system's controls or drawn by the framework or a web page; the vertical axis is how much runtime the app carries (a browser engine, a JS engine, a rendering engine, a language runtime).

Technology

How ten frameworks work: the languages for UI and logic, how the UI is drawn, what ships with the app, and the license

Platforms and capabilities

Platforms and capabilities: support for macOS, Windows, Linux, mobile and the web, plus native look, built-in MCP and compile-time checks; Bern's mobile support is partial and it has all three capabilities
  • Mobile: iOS and Android. React Native's desktop versions are maintained by Microsoft; uni-app also covers mini programs and HarmonyOS; Bern's Android target runs on devices and makes packages, its iOS target builds simulator apps, and iOS device packages are planned.
  • Native look: the controls are the platform's own, with look, accessibility and input methods from the system. Qt Widgets and uni-app x come close and count as partly.
  • Built-in MCP: an MCP server and command line that come with the framework and act on the running interface. Other frameworks can be automated from outside through accessibility APIs or test tools such as Playwright and Flutter Driver.
  • Compile-time checks: a missing translation, a feature without an entry in some layout, or an entry not registered to a feature is a compiler error (see Built for AI). Other frameworks mostly find these with linters, translation tools or tests.
  • Maturity: Electron and Qt are very mature; Flutter, React Native, uni-app, Tauri and Avalonia are mature; Dioxus is developing quickly; GPUI powers the Zed editor while its general-purpose components are early; Bern is an early preview.

Performance and footprint

The languages of the UI and logic layers, and how they run, set the ceiling of runtime overhead: natively compiled languages without garbage collection cost the least; JIT-run and interpreted languages ship an engine and use more memory.

Languages and execution: Bern's UI layer is each platform's native UI code (Swift, C++, Rust) and its logic layer is Rust, called in process through a C ABI, with no garbage collection

For the same window showing one line of text, package size, launch time and memory differ widely. The chart is measured on one CI virtual machine (a GitHub-hosted Apple M1): each framework's own tools build a release package, and the apps take turns starting, ten times each, median shown. The scripts are in benchmarks/; the Benchmarks workflow reruns them from the Actions tab.

Hello World footprint, measured: Bern 2.1 MB, 0.26 s, 18 MB; GPUI 4.2 MB, 0.28 s, 27 MB; Electron 301 MB, 1.07 s, 77 MB

Which one to choose

Which one to choose: what Electron, Tauri, Flutter, React Native, Avalonia, Qt, GPUI, Dioxus, uni-app and Bern are best for, and what each costs

Quick start

You need Rust 1.99 (rustup installs it from rust-toolchain.toml) plus the toolchain of the target you build. First install the build tool bern from source:

git clone https://github.com/AimesSoft/Bern
cd Bern
cargo install --path crates/bern-cli

Create an app

In any directory:

bern new my_app
cd my_app
bern run

The new app is a window with a counter, with one crate each for the protocol, the engine and the interface, and all six targets declared in Bern.toml. bern run builds the target for this computer and starts it: SwiftUI on macOS, WinUI 3 on Windows, GTK on Linux; bern run --target web_app opens it in the browser, bern run --target ios installs it in the running iOS simulator, and bern run --target android installs it on the Android phone connected with USB debugging (or an emulator) and starts it. Every build also produces the application CLI and MCP server in target/bern/<target>/bin/.

bern doctor lists what this computer still needs for each target, and how to install it:

$ bern doctor
bern 0.1.0 on macOS (aarch64)

[✓] Rust: rustc 1.99.0, cargo 1.99.0
[✓] macOS · SwiftUI: macOS SDK 26.5, Swift 6.3.2
[✓] iOS · SwiftUI: iOS simulator SDK 26.5, the aarch64-apple-ios-sim target
[-] Windows · WinUI 3: builds on Windows
[-] Linux · GTK 4: builds on Linux
[✓] Android · Compose: Android SDK, NDK 29.0.14206865, gradle-9.5.1, the aarch64-linux-android target
[✓] Web · WebAssembly: the wasm32-unknown-unknown target
What each platform needs
  • macOS: Xcode 26 or later. After installing it from the App Store, run sudo xcode-select --switch /Applications/Xcode.app.
  • iOS: Xcode's iOS platform and an iOS simulator, plus rustup target add aarch64-apple-ios-sim. Builds are for the simulator for now; device signing and packages are not done yet.
  • Windows: Visual Studio 2022 or later with “Desktop development with C++” (the Build Tools are enough) and a Windows 11 SDK; Rust on the MSVC toolchain. The first build generates the C++/WinRT headers with the Windows SDK's cppwinrt and precompiles them with the runtime (about 2.4 GB per profile, under target); later builds compile only the app's own code, about 5 s for a new app and 9 s for the showcase. The app folder holds only the executable, a 400 KB bootstrapper DLL and the resources: no .NET, no bundled Windows App SDK.
  • Linux: GTK 4.14 and libadwaita 1.5 development packages, for example sudo apt install libgtk-4-dev libadwaita-1-dev gstreamer1.0-libav.
  • Android: Android Studio with the SDK and the NDK (SDK Manager → SDK Tools → NDK), Gradle 9.4 or newer, and rustup target add aarch64-linux-android. The first build has Gradle download the Android plugin and the Compose libraries.
  • Web: rustup target add wasm32-unknown-unknown. A web_app build compiles the engine to WebAssembly and runs it in the page; a web_ui build makes the page the UI of a separate engine host, which bern run starts before opening its address.

Ship an app

bern package

Builds every target this computer can in release and writes packages to target/bern/dist/: a .dmg for macOS, an installer (per user, no administrator rights) and a .zip for Windows, a .deb and a .tar.gz for Linux, an .apk and an .aab for stores for Android, and an archive of the static site for the Web. Every package but Android's carries the app's CLI and MCP server. Signing comes from the environment, so certificates stay out of the repository: BERN_MACOS_SIGNING_IDENTITY and BERN_MACOS_NOTARY_PROFILE (sign and notarize), BERN_WINDOWS_CERTIFICATE (a certificate thumbprint or a .pfx), BERN_ANDROID_KEYSTORE, BERN_ANDROID_KEYSTORE_PASSWORD and BERN_ANDROID_KEY_ALIAS (the release key). Without them the packages are still made, with a note on what being unsigned means.

Run the showcase

The showcase in the Bern repository uses every component, two windows and a video player. From the repository directory:

cd examples/showcase
bern run

Drive the running app

showcase ui inspect                                          # every mounted control
showcase ui select --target main_window.pages --option Controls
showcase ui set-bool --target controls_page.sync --value true
showcase showcase search-library --text mo                   # an engine query
showcase window open --kind settings
showcase window list                                         # actual window state
showcase-mcp                                                 # MCP server over stdio

The control channel starts only when the target declares channels = ["ipc"]; without it the app listens on nothing. An Android app listens on the phone; bern run joins it to this computer with adb forward, so the CLI and MCP server work as usual.

Roadmap

Roadmap: phases 1 to 4 and phase 7, style, motion, modular apps and media app components, are done; phase 5 other native front ends and phase 6 components and tooling are in progress

Repository

Crates and directories
Repository: the crates and directories in core, runtime, targets, entries and tools, and examples and docs

Development

cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace

Tests include declarations that must fail to compile, and type-check the generated apps where the toolkit is installed: Swift 6 with Xcode, GTK with its development packages, both generated front ends with MSVC on Windows. Android's Kotlin is compiled by Gradle in bern build --target android.

Documentation

The design and implementation documents are written in Chinese.

License

MIT © 2026 MCDFSteve


Bern is in early development. APIs and the declaration syntax may still change.

About

One Rust declaration, compiled into each platform's own native UI: SwiftUI, WinUI 3, GTK 4 and the Web. Built for AI: what an agent forgets fails to compile, and every button is an MCP tool. No JS engine, no embedded browser.

Topics

Resources

Contributing

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages