Library / SDK
phronmophobic/membrane avatar
phronmophobic/membrane

Membrane: a Clojure UI library that runs on the desktop and the web

A Simple UI Library That Runs Anywhere

645 stars20 forksClojureApache-2.0

At a glance

What is it?
Membrane is a UI library for Clojure and ClojureScript built on the idea that views should be values and event handlers should be pure functions, with a platform agnostic graphics model underneath and backends for macOS, Linux and WebGL. It is a beta, the desktop rendering leans on Skia, and the interesting part is the layering rather than any single platform.
Who is it for?
Membrane fits a Clojure developer who wants a desktop application with the functional style of re-frame or fulcro, and who is prepared to build on a beta release and to supply Skia native libraries for the target platform. It is the wrong choice if you need a mature, widely packaged toolkit, since the version you add is a 0.14 beta, and the wrong choice if you need the whole platform list to be supported rather than experimental.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 71 days ago.
What is it written in?
Mainly Clojure, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Written to avoid the impedance mismatch of a toolkit

The Rationale section of the README is the most direct statement of intent in the project. Membrane was written because the author wanted to build a desktop app with Clojure while having the functional features found in libraries targeting the browser, and it names react, reagent, re-frame and fulcro as the things worth having.

The design decision that follows is that Membrane does not build on top of any existing UI toolkit like Swing, JavaFX, HTML, UIKit or GTK. The stated reason is that these toolkits are fundamentally based on an object oriented model and have a deep impedance mismatch with idiomatic Clojure code.

That is a real trade and the README states the consequence in two lines: views are values, and components and event handlers are pure functions. It also means there is no Swing or GTK underneath, so the rendering has to be built on something else, which is where Skia comes in later.

For a Clojure developer this is the whole pitch. The alternative to using Membrane is either to accept an object oriented toolkit and write imperative code inside a Clojure shell, or to give up the desktop and build in a browser. Membrane is the third option, and it comes with the cost of being a young library with its own rendering stack.

Three layers, and you can mix them

Membrane provides all the tools needed to build a user interface, and the README is careful about what that means. While all the pieces are built to work together, each piece is optional or can be mixed and matched with other implementations.

The three main pieces are listed as a UI framework, `membrane.component`, that provides state management for GUIs; a platform agnostic model for graphics and events; and multiple graphics backends that provide concrete implementations for the model.

The examples of mixing are what make this a design rather than a product. You could use your favorite UI framework and the other layers to reach another platform, or you could provide your own ncurses graphics backend and leverage the UI framework and the graphics model.

The requirement for running on a platform is correspondingly small. There are only two: drawing implementations for graphical primitives like shapes, text and images, and an event loop that forwards events such as mouse clicks and key presses to Membrane and repaints. Everything else is optional.

That is the architectural claim of the project, and it is the one to test first if you are evaluating it. If the two requirements are cheap for your platform, the rest of the stack is portable by construction. If they are not, no amount of the higher layers will save you.

Three supported platforms and a longer list of experiments

The platform list is split in two, and the split is the honest assessment of the project.

The supported platforms are Mac OSX, Linux, and the web via WebGL. Everything else is listed as experimental, and the list is long: Windows, with the skija, swing and javafx backends; the terminal, with extra documentation and an example project; virtual dom; skija itself, with an example project; javafx via cljfx, with an example project; and iOS via graalvm, also with an example project.

There is a second experimental category for UI framework integrations, listing re-frame and fulcro with their own example projects. That list matters more than it first appears, because it is the direct consequence of the layering. If you already use re-frame, the Membrane UI framework is not forced on you, and the README points at a membrane-re-frame-example to show that combination.

The picture for an adopter is therefore: the desktop and web are the paths the project stands behind, the terminal and the other backends are paths the project demonstrates, and a framework integration is a supported way of substituting the top layer.

The web-via-WebGL entry is worth pausing on too. A browser target that goes through WebGL rather than the DOM is unusual, and it follows from the same constraint as everything else: the graphics model is the platform agnostic one, so the browser is reached by implementing that model on top of WebGL.

Skia is the rendering substrate on the desktop

The Usage section shows how the library is added, and it also shows the shape of the native dependency problem.

For Leiningen the dependency is:

clojure
[com.phronemophobic/membrane "0.14.4-beta"]

and for deps.edn it is:

clojure
com.phronemophobic/membrane {:mvn/version "0.14.4-beta"}

The skia backend needs native dependencies matching the target platform, and the README lists the coordinates:

clojure
com.phronemophobic.membrane/skialib-macosx-x86-64 {:mvn/version "0.14-beta"}
com.phronemophobic.membrane/skialib-linux-x86-64 {:mvn/version "0.14-beta"}
com.phronemophobic.membrane/skialib-macosx-aarch64 {:mvn/version "0.14-beta"}

The note that follows is the practical instruction: if multiple native dependencies are included, it will automatically choose the right one. So a build can carry several platform libraries and pick at runtime, which is what makes a single artifact distributable across macOS on Intel, macOS on Apple Silicon and Linux on x86-64.

Two things to watch. The native libraries are versioned separately from the library itself, at 0.14-beta against the library's 0.14.4-beta, so the two have to be kept in step manually. And the set of coordinates is the set of platforms the desktop path covers, which is a more precise answer to the question of where this runs than the platform list in the README.

A counter, written as a value and a pure handler

The first example in the README is labelled a simple example without the UI framework, and it is worth reading in full because it shows the shape of the model.

The namespace requires `membrane.java2d` for the window and `membrane.ui` for the layout primitives:

clojure
(ns counter
  (:require [membrane.java2d :as java2d]
            [membrane.ui :as ui
             :refer [horizontal-layout
                     button
                     label
                     spacer
                     on]]))

Then the state is an atom, and the view is a function of a number:

clojure
(defonce counter-state (atom 0))

(defn counter [num]
  (horizontal-layout
   (on :mouse-down (fn [[mouse-x mouse-y]]
                     (swap! counter-state inc)
                     nil)
       (button "more!"))
   (spacer 5 0)
   (label num (ui/font nil 19))))

Three things are visible in those lines. The view takes a number and returns a layout, so it is a pure function of its argument. The event handler is a pure function that takes the event and returns a side effect, here a `swap!` on the atom, and it returns nil. And the rendering is a separate concern, invoked through `java2d/run` in the commented-out block that pops up the window showing the counter.

The README also shows a second version of the same counter built on `membrane.component`, the UI framework that provides state management, with `defui` and `m` in the refer list. The difference between the two examples is the whole argument for the top layer: without it you manage the atom and the repaint yourself, with it the framework owns the state and the update cycle.

A beta library with a decade of real applications

The version you install is 0.14.4-beta, and the README does not pretend otherwise. The library is in beta, the platform list mixes supported and experimental, and the native dependencies carry their own beta version. Anyone adopting this is taking a bet on a single maintainer's project.

What balances that is the Built With Membrane list, which is not a toy list. It includes membrane.term, described as a simple terminal emulator in Clojure, and browser and htmltoimage, which use clj-cef to convert a URL to an image and to build a very simplistic browser using Membrane with skija. There is clj-media for viewing or creating videos and gifs in Clojure, with codetogif as a basic example, snowball for viewing the sizes of your dependencies, viscous as a visual data inspector that runs in constant space, time and screen space, treemap-clj as an alternative to pprint for visualizing heterogeneous hierarchical data, and grease, the example of building a Clojure library for iOS with graalvm native-image.

A terminal emulator, a video tool and a data inspector are not things you write to test a UI library. They are things you write when the library is the easiest way to get the job done.

The documentation around the project is unusually complete for its size. There is a tutorial, an API reference, documentation organised by topic, a page on styled text, a distribution guide for desktop apps, a WebGL guide, and a background post on what a user interface is, plus a video on interactive programming with Clojure and Membrane.

The release naming is the weakest signal in the repository

The recent releases are listed as release-8500854981 from 2024-03-31, release-8500403535 from the same day, and release-5557046966 from 2023-07-14.

Those are not semantic version tags, they look like identifiers from a continuous integration or release pipeline, and the practical effect is that there is nothing in the release history a consumer can read. There is no changelog line to inspect, no version number to compare, and no way to tell from the tag list whether a change was a fix or a rewrite. The version that the README tells you to depend on, 0.14.4-beta, does not appear in the release list at all.

That is a genuine argument for pinning an exact version in your build file and for reading the upstream commits rather than the release notes when you upgrade, and it is the part of the repository that a prospective user should raise with the maintainer.

The licence is Apache-2.0, with the LICENSE file at the root. The repository is not archived and the last push was on 2026-08-12.

The layout tells you how the project is built, and it is a multi-toolchain one: a `src` directory for the Clojure code, `src-java` and `csource` for the Java and C sides that the graphics backends need, `native-image` for the graalvm path, `notebooks` for the literate examples, and both `project.clj` and `deps.edn` with a `build.clj` and a `shadow-cljs.edn` for the ClojureScript target. There are four example projects in the examples directory, named gol, readme, rss and tutorial, which is the fastest way to see the layers combined.

Editorial conclusion

Membrane fits a Clojure developer who wants a desktop application with the functional style of re-frame or fulcro, and who is prepared to build on a beta release and to supply Skia native libraries for the target platform. It is the wrong choice if you need a mature, widely packaged toolkit, since the version you add is a 0.14 beta, and the wrong choice if you need the whole platform list to be supported rather than experimental. Verify first which graphics backend you will actually target, because Windows, the terminal, iOS and the other integrations are listed as experimental, and pin the version of the native Skia libraries to match the platform you build on.

Frequently asked questions

What is Membrane and what is it for?

The README describes Membrane as a simple UI library that runs anywhere, built to provide a UI framework with state management, a platform agnostic model for graphics and events, and multiple graphics backends. It was written to give a Clojure developer the functional features of browser libraries for a desktop application.

Which platforms does Membrane support?

The supported platforms are Mac OSX, Linux and the web via WebGL. Windows, the terminal, virtual dom, skija, javafx via cljfx and iOS via graalvm are listed as experimental, as are the re-frame and fulcro UI framework integrations.

How do I add Membrane to a Clojure project?

With Leiningen, [com.phronemophobic/membrane "0.14.4-beta"], or with deps.edn, com.phronemophobic/membrane {:mvn/version "0.14.4-beta"}. To use the skia backend you also add the native library coordinates for your platform, such as skialib-macosx-aarch64 or skialib-linux-x86-64.

What is the idea behind Membrane's UI model?

The README states it directly: views are values, and components and event handlers are pure functions. Membrane deliberately does not build on Swing, JavaFX, HTML, UIKit or GTK, because those toolkits are object oriented and clash with idiomatic Clojure code.

Is Membrane a stable release?

No. The version the README tells you to depend on is 0.14.4-beta, and the native skia libraries are versioned separately at 0.14-beta. The recent GitHub releases are named with pipeline identifiers rather than version numbers, so pinning an exact version and reading commits is the practical approach.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. phronmophobic/membrane on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/phronmophobic-membrane.svg)](https://hysenlabs.com/projects/phronmophobic-membrane)

Was this useful?