5 unstable releases

Uses new Rust 2024

0.7.1 May 9, 2026
0.7.0 May 9, 2026
0.4.1 Mar 22, 2026
0.4.0 Mar 22, 2026
0.0.1 Mar 21, 2026

#292 in GUI

MIT/Apache

2MB
44K SLoC

plushie

Build native desktop apps in Rust. Pre-1.0

Write your entire application in Rust (state, events, UI) and get native windows on Linux, macOS, and Windows. The renderer is built on iced and can run in-process (no subprocess needed) or as a separate binary over stdin/stdout.

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

Quick start

use plushie::prelude::*;

struct Counter { count: i32 }

impl App for Counter {
    type Model = Self;

    fn init() -> (Self, Command) {
        (Counter { count: 0 }, Command::none())
    }

    fn update(model: &mut Self, event: Event) -> Command {
        match event.widget_match() {
            Some(Click("inc")) => model.count += 1,
            Some(Click("dec")) => model.count -= 1,
            _ => {}
        }
        Command::none()
    }

    fn view(model: &Self, _widgets: &mut WidgetRegistrar) -> ViewList {
        window("main").title("Counter").child(
            column().spacing(8.0).padding(16)
                .child(text(&format!("Count: {}", model.count)).size(24.0))
                .child(row().spacing(8.0).children([
                    button("inc", "+").style(Style::primary()),
                    button("dec", "-").style(Style::danger()),
                ]))
        ).into()
    }
}

fn main() -> plushie::Result {
    plushie::run::<Counter>()
}
[dependencies]
plushie = "0.6"

Direct mode (default) embeds the renderer. For wire-only (lighter build):

[dependencies]
plushie = { version = "0.6", default-features = false, features = ["wire"] }

default-features = false without either runner feature (direct or wire) is a library-only build. It supports APIs such as queries and TestSession, but plushie::run() returns Error::NoRunnerFeature because no renderer runner is compiled in.

Run the examples with cargo run -p plushie --example counter.

How it works

Your Rust application and the renderer share a process by default (direct mode). 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. In wire mode, the renderer runs as a separate process and communication happens over stdin/stdout, using the same protocol that powers the Elixir, Gleam, Python, Ruby, and TypeScript SDKs.

Features

  • Elm architecture - init, update, view with typed events and commands
  • Built-in widgets - layout, input, display, and interactive widgets out of the box
  • Canvas - shapes, paths, gradients, transforms, and interactive elements for custom 2D drawing
  • Themes - dark, light, nord, catppuccin, tokyo night, and more, with custom palettes and per-widget style overrides
  • Animation - renderer-side transitions, springs, and sequences with no wire traffic per frame
  • Multi-window - declare windows in your view; the framework manages the rest
  • Platform effects - native file dialogs, clipboard, OS notifications
  • Accessibility - keyboard navigation, screen readers, and focus management via AccessKit
  • Custom widgets - compose existing widgets, draw on the canvas, or implement PlushieWidget in Rust
  • Two rendering modes - direct (in-process, default) or wire (subprocess binary via stdin/stdout)

Testing

Use TestSession for headless testing without a display:

use plushie::test::TestSession;

let mut session = TestSession::<Counter>::start();
session.click("inc");
session.click("inc");
assert_eq!(session.model().count, 2);
session.assert_text("count", "2");

Status

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

License

MIT OR Apache-2.0

Dependencies

~4–57MB
~867K SLoC