shirei: an immediate-mode GUI framework that lays out like the web
Go based GUI framework: practical, immediate mode, flexbox model
At a glance
- What is it?
- shirei is a cross-platform Go GUI framework where you describe the interface as a function of state, get flexbox-like layout for free, and ship a small native binary instead of an Electron app. It suits utility tools written by people who want a native window without a web stack, and it needs Go 1.25 and a real GPU story on some platforms.
- Who is it for?
- shirei fits a Go developer building a utility application who wants a native window, a small binary and a declarative API without maintaining a widget tree or shipping a web runtime. It is the wrong choice for a multi-window app, for a program that needs its own GPU surfaces for video or 3D, and for anyone on an older Go toolchain, since the module requires Go 1.25.0.
- Can I use it commercially?
- Yes. Zlib 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 29 days ago.
- What is it written in?
- Mainly Go, 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
Immediate mode defined as an API choice, not a rendering trick
shirei is a cross-platform GUI framework for Go, positioned as a lightweight alternative to web-based approaches with a focus on development ergonomics. The four points in the README's opening list define the shape of the whole thing.
You build the UI in pure Go rather than HTML and JavaScript. You describe the UI as a function of state rather than creating and maintaining widget objects. You get the good parts of the web model, a uniform container tree and flexbox-like layout. And custom components can keep internal state that the caller does not have to care about.
The motivation section is unusually precise about what immediate mode means here, and the precision matters for anyone coming from another GUI library. The README says that when it says immediate mode it is not talking about the rendering mechanism, it is talking about the API: do you retain widgets in your application code, or do you just say what the UI should look like right now given application state? The author adds that existing GUI frameworks in Go all seem to require you to retain widgets yourself, even the ones that say they are immediate mode.
That is the claim to evaluate. Shirei combines two ideas: build the UI with regular code constructs and respond to input without callbacks, and automatically size and layout elements based on a flexbox-like system.
The audience is Go developers writing utility applications who would otherwise reach for a terminal UI framework because they dislike the weight of Electron. The README's own phrasing is that if you find yourself resorting to TUI frameworks because you dread using Electron, give shirei a try.
The view is a function, and input comes back as return values
The example in the README shows the API in about twenty lines, and it is worth reading closely because the ergonomics claim rests on it.
func ProfileForm(profile *Profile) {
Container(Attrs(Expand, Pad(18), Gap(8)), func() {
Label("Profile", FontSize(22),
FontWeight(WeightBold))
Label("Name")
TextInput(&profile.Name)
Label("Email")
TextInput(&profile.Email)
Container(Attrs(Row, CrossMid, Gap(10)), func() {
if Button(SymITick, "Save") {
profile.Saved = true
}
if profile.Saved {
Label("All changes saved",
FontSize(12),
TextColor(145, 55, 34, 1))
}
})
})
}Three things are happening. The view function is called every frame and rebuilds the tree from the current state, so there is no retained widget object to keep in sync. Input is returned rather than delivered, since `if Button(SymITick, "Save")` reads as a condition on the button's return value, and there is no callback to wire up. And the layout attributes are passed as a chain, with Attrs collecting Expand, Pad and Gap in one call, and Container taking a closure that describes its children.
The TextInput calls take a pointer to the field, so the widget writes directly into the caller's state. That is the one place where the framework is not purely functional, and it is the ergonomic trade that makes text editing bearable.
The two Container calls show the tree structure directly, and the attribute vocabulary is CSS-like: Expand, Pad, Gap, Row, CrossMid. The README mentions an appearance and colour guide in the documentation list, so the styling surface is documented separately from the widget calls.
A small native binary and a GPU compositor on every platform but X11
The distribution story is what makes this interesting for utility software. The README says the goal is to produce small native binaries, and that around 10MB is typical for binary size. That figure comes with a design intent stated alongside it, that performance and resource usage is a first class design consideration.
The rendering path is more specific than most frameworks document. A GPU compositor is used by default on macOS with Metal, on iOS with Metal, on Wayland with GLES, on Android with GLES, on Windows with D3D11, and on the web with WebGL2. X11 stays software. Setting SHIREI_GPU=0 or an init failure falls back to the software renderer.
That X11 line is the one to note if you deploy on Linux desktops, because much of the installed Linux base is still X11. A framework that renders in software on X11 and on the GPU on Wayland gives you a different performance profile depending on the session type, and the README does not attempt to hide that.
The platform list is long: Windows, macOS, Linux on Wayland and X11, iOS for iPhone, and Android. Cross compilation works for desktop platforms without CGO, including macOS, and iOS still requires cgo. That is a useful distinction, because CGO-free cross compilation is what lets you build release binaries from a Linux CI runner for Windows and macOS, and the fact that iOS is excluded from that is stated rather than glossed over.
The go.mod shows the module path is go.hasen.dev/shirei, it requires Go 1.25.0, and the dependency list is modest. Notable entries are go-text/typesetting for text shaping, golang.org/x/text and x/image for text and image handling, jezek/xgb for X11, godbus for D-Bus, ebitengine/purego for calling into native libraries, cli/browser for opening URLs, and two of the author's own modules, go.hasen.dev/textsearch and go.hasen.dev/udplib.
Snapshot testing without a window, and driving the app by name
Two testing features stand out, and both come from the same underlying decision: the framework can render a frame without a native window.
Snapshot testing works by rendering the normal application frame to an image without opening a native window or requiring a GPU, and headless snapshots always use the software renderer. For a GUI framework, that is the difference between having golden-image tests and not, because a test that needs a display server cannot run in a normal CI container. A frame_trace.go file and the image and colour test files at the repository root suggest this is a maintained part of the design rather than a recent addition.
The second feature builds on the same access tree. Access attributes on containers, named NextAccessName and AssignAccess, build a queryable tree each frame. Tests can then launch the app and click by name over loopback UDP, with a tutorial at docs/drive-tutorial.md and a command reference at docs/drive.md.
That is a genuinely unusual combination for a Go GUI library: a way to assert on rendered output, and a way to script interaction with the running application. For a project with meaningful interface logic, that is often the difference between a framework you can adopt and one you cannot, because the alternative is writing the tests against your own state rather than against what the user sees.
The release history suggests this area is being worked on. Version 0.6.12 and version 0.7.0 were both published on 2026-09-18, and both carry the title Basic desktop screen-reader support, and version 0.8.0 followed on 2026-09-24 with color schemes and widget styling. Two releases on the same day with the same title, one of them a patch and one a minor, suggests a release train that was cut and then corrected.
The limitations section is short and specific
The README has a Limitations section, and it is worth reading in full because it is short and concrete rather than hedged.
Shirei apps only have one window with standard decorations. No docking, no multi-document, no custom window chrome. For the utility-application audience the project targets, that is usually enough, but for anything modelled on a full desktop application it is disqualifying.
Basic screen-reader support on macOS, Linux Wayland and 64-bit Windows covers static text, buttons, toggles and sliders, and the README points to a documentation page for the scope and the testing involved. The list of covered elements is the important part. Static text, buttons, toggles and sliders is not a general accessibility story, and a product that must pass an accessibility review will find that the framework is not the blocker but the coverage is.
There are no app-owned GPU surfaces, which means video and custom 3D are out, and the README says those are under consideration rather than planned.
Two more constraints come from the platform sections rather than this list. The X11 session is software rendered, and iOS builds still require cgo, which means the CGO-free cross compilation story has a hole in it.
The international text support claim is the counterpart to the accessibility limits, and it is unusually strong for a Go GUI framework. The README says that unlike what you might expect from imgui libraries from C or C++, shirei supports complex text shaping and bidirectional layout, an input method editor for East Asian languages, and the ability to use all system fonts. The font handling code at the repository root, with per-platform critical font lists for Android, Darwin, iOS, Linux and Windows plus fallback logic, backs that up.
Getting a window on screen: the module, the init and the run
The getting started section asks you to copy a main.go into a new folder, and it is short enough to reproduce in full.
package main
import (
"fmt"
app "go.hasen.dev/shirei/app"
. "go.hasen.dev/shirei"
. "go.hasen.dev/shirei/widgets"
)
func main() {
app.SetupWindow("My App", 300, 100)
app.Run(RootView)
}
var count int
func RootView() {
Container(Attrs(Viewport, Background(220, 10, 97, 1)), func() {
Container(Attrs(Row, CrossMid, Pad(20), Gap(10)), func() {
Label(fmt.Sprintf("Counter: %d", count))
if Button(SymIPlus, "Increment") {
count++
}
})
})
}Two details are worth pulling out. The window is set up with app.SetupWindow, taking a title and a width and height, and then app.Run is given the root view function. The view is a plain Go function, RootView, which closes over a package-level count variable, and the button's return value is used to increment it. There is no retained widget and no callback, and the next frame redraws the label with the new value.
The imports use dot imports for the framework root and the widgets package, which is why the calls read as bare Container, Label and Button. That is a deliberate style choice, and it is also a real constraint: dot imports pull every exported name into the file's scope, so the README's own naming advice and your project conventions have to live with it.
After copying the file, three commands set up the module and run it:
$ go mod init main
$ go mod tidy
$ go run .The go mod tidy step is what resolves the framework from its module path, and the README notes that a program can also be run with go run . or go run ./pkg.
Two companion CLIs cover mobile installs and release packaging
The repository ships two command line tools, and they split the mobile and release workflows cleanly.
shirei_mobilerun is the development tool, described as dev installs on iOS and Android with debug signing and fast iteration. shirei_bundle is the release tool, described as release packaging producing an IPA, a release APK, a macOS zip or pkg, and desktop archives. The README gives the install commands:
go install go.hasen.dev/shirei/cmd/shirei_mobilerun@latest
go install go.hasen.dev/shirei/cmd/shirei_bundle@latestThe binaries land in the directory the GOPATH environment variable points at, and the README notes that this needs to be on your PATH.
The documentation list backs the claim that this is a real mobile workflow rather than a stub. There are separate documents for Android and iOS dev setup, plus a bundle details page and a mobilerun details page, and an application resources document covering the Resources directory and the app.ResourcePath helper.
For context on scale, the repository root is wide rather than deep in the usual library sense. There are backend directories for androidbackend, cocoabackend and gpurender, an audio directory, a drive directory for the loopback UDP test driver, a cmd directory for the two tools, an examples directory, a demos directory, an ext directory, and a long list of test files at the top level, including focus, input, identity, glyph cache and colour tests. The presence of a docs directory with a tutorial, a layout tutorial and an appearance guide is what you would expect from a project that wants to be adopted rather than admired.
Where shirei is the wrong tool
The first wrong-tool case is a multi-window application. The Limitations section says it plainly: one window, standard decorations. A tool that docks, that opens documents in separate windows, or that wants a custom title bar is out of scope, and the README does not pretend otherwise.
The second is anything that needs its own GPU surface. Video playback and custom 3D are excluded, and the README describes app-owned GPU surfaces as under consideration. If your application is a media player or a 3D viewer, a framework built around drawing a widget tree is the wrong shape for it.
The third is a Linux deployment on X11. The GPU compositor is on by default for Wayland and off for X11, so on a fleet of X11 desktops you are on the software renderer while the same binary on a Wayland session is not. Performance-sensitive Linux applications should be measured on the session type they will actually run in.
The fourth is a Go toolchain below 1.25.0. The module requires that version, which rules out older CI images and older developer machines unless you upgrade them. That is a real migration cost for an organisation that standardises on an older Go.
The fifth is a product with an accessibility requirement that goes beyond static text, buttons, toggles and sliders. The framework provides basic screen-reader support on three desktop platforms, and a product that must meet a conformance level will need to know exactly what is covered before committing.
The alternative to weigh is a web stack, which the README names directly: you would build the interface in HTML and JavaScript, in a browser, with the layout you already know. The difference in approach is that shirei gives you native binaries of roughly 10MB and no browser, while a web stack gives you the entire web ecosystem, CSS, and the cost of a runtime and a windowing environment. And the other alternative is a retained-mode Go GUI library, where you keep widget objects and update them, which is more ceremony in the code but gives you a stable object model rather than a redraw-every-frame one.
Editorial conclusion
shirei fits a Go developer building a utility application who wants a native window, a small binary and a declarative API without maintaining a widget tree or shipping a web runtime. It is the wrong choice for a multi-window app, for a program that needs its own GPU surfaces for video or 3D, and for anyone on an older Go toolchain, since the module requires Go 1.25.0. Before adopting it, check the accessibility scope document for the platform you target, confirm your Linux session is Wayland rather than X11 if you care about GPU rendering, and try the snapshot testing path, since rendering a frame without a window is the fastest way to see whether the framework fits your testing setup.
Frequently asked questions
What is shirei and what kind of GUI does it build?
It is a cross-platform Go GUI framework where the interface is described as a function of application state, with a uniform container tree and flexbox-like layout, producing native binaries of around 10MB.
Which platforms does shirei support and how does it render?
It supports Windows, macOS, Linux on Wayland and X11, iOS and Android, using a GPU compositor by default on macOS, iOS, Wayland, Android, Windows and the web, with X11 left on the software renderer and SHIREI_GPU=0 as the way to force it.
How do I test a shirei application without a display?
The framework supports snapshot testing by rendering the normal application frame to an image without opening a native window or requiring a GPU, and headless snapshots always use the software renderer.
What are the stated limitations of shirei?
Apps have only one window with standard decorations, there is no app-owned GPU surface so video and custom 3D are excluded, and screen-reader support covers static text, buttons, toggles and sliders on macOS, Wayland and 64-bit Windows.
Official sources
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.
[](https://hysenlabs.com/projects/hasenj-go-shirei)Was this useful?