A GUI library for Go that doesn't make you want to switch to web dev.
Thank you to the early adopters who supported Proton from the very beginning!
![]() @VioGrafu (First Stargazer) |
![]() @bigwhite |
![]() @TanmayCzax |
![]() @aurax |
![]() @DemonK1 |
![]() @pekim |
![]() @fbaube |
![]() @gorilacrocodille |
![]() @alanmsant2 |
Official Docs: https://nexus-65.gitbook.io/proton
package main
import "github.com/CzaxStudio/proton"
func main() {
a := proton.New("hello")
a.Window("Hello", 400, 200, func(ctx proton.Context) {
proton.H3(ctx, "Hello from Proton!")
})
a.Run()
}go get github.com/CzaxStudio/proton
Then run once to pull Gio's dependencies:
go mod tidy
Linux — three system packages required:
sudo apt install libwayland-dev libxkbcommon-dev libvulkan-devmacOS and Windows need nothing extra.
Your draw function runs every frame. Call widget functions in order — they stack vertically by default. State lives in your own struct. No setState, no component trees, no XML.
type UI struct {
btn proton.Clickable // button
name proton.Editor // text input
checked proton.Bool // checkbox / toggle
choice proton.Enum // radio group
vol proton.Float // slider
scroll proton.Scrollable // list / scroll area
}Label H1–H6 Body2 Caption Text Muted ColoredText ErrorText SuccessText WarningText
Button OutlineButton IconButton Tappable Link LinkSmall
Input TextArea Checkbox Toggle RadioButton Slider ProgressBar NumberInput SelectBox SearchInput
List HList Scroll TextView LogView
Row Column RowSpread RowEnd GrowRow GrowColumn GrowItem FixedItem FlexSpacer
Split HSplit ResizeSplit ResizeHSplit Center ZStack
Pad PadH PadV PadSides Gap Grid MinSize MaxWidth
Divider LabeledDivider Rect RoundRect Card HoverCard Badge StatusDot
Avatar Tag Image Logo CodeBlock ShortcutHint ColorSwatch
Table ProgressRing Stepper
Toast Alert AlertDismissable Tooltip Spinner
Overlay Tabs Accordion ContextMenu
If OnKey FocusArea
// side by side
proton.Row(ctx,
func(ctx proton.Context) { proton.Label(ctx, "left") },
func(ctx proton.Context) { proton.Label(ctx, "right") },
)
// one child fills remaining space
proton.GrowRow(ctx,
proton.FixedItem(ctx, func(ctx proton.Context) { proton.Label(ctx, "Search:") }),
proton.GrowItem(ctx, func(ctx proton.Context) { proton.Input(ctx, &e, "") }),
proton.FixedItem(ctx, func(ctx proton.Context) { proton.Button(ctx, &b, "Go") }),
)
// draggable split pane
proton.ResizeSplit(ctx, &u.split, 0.35, leftFn, rightFn)
// padding
proton.Pad(ctx, 16, func(ctx proton.Context) { ... })
proton.PadSides(ctx, 8, 16, 8, 16, func(ctx proton.Context) { ... })
// blank space
proton.Gap(ctx, 12)46 built-in palettes. One line to apply any of them.
a.ApplyPalette(proton.NordPalette)
a.ApplyPalette(proton.CatppuccinPalette)
a.ApplyPalette(proton.DraculaPalette)
a.ApplyPalette(proton.TokyoNightPalette)
a.ApplyPalette(proton.GruvboxDarkPalette)
a.ApplyPalette(proton.RosePinePalette)
// ... 40 morea.ApplyPalette(proton.Palette{
Bg: proton.RGB(0x1e1e2e),
Fg: proton.RGB(0xcdd6f4),
Primary: proton.RGB(0x89b4fa),
PrimaryFg: proton.RGB(0x1e1e2e),
})No structs needed — just pass the hex string:
a.ThemeBuilder().
Bg("#1e1e2e").
Fg("#cdd6f4").
Primary("#89b4fa").
PrimaryFg("#1e1e2e").
Apply()
// patch one color on the current theme
a.ColorCode("primary", "#ff6b6b")
a.ColorCode("bg", "#0d1117")Accepted formats: "#rrggbb", "rrggbb", "#rgb", "#rrggbbaa".
a.SetBackgroundCode("#1a1b26")
a.SetBackgroundRGB(26, 27, 38)
a.SetBackgroundGradient("#1a1b26", "#2d1b69", "vertical")
a.SetBackgroundRainbow() // animated full-spectrum gradienta.SetFontScale(1.1)Drop into a settings window to let users switch themes at runtime:
type UI struct {
picker proton.ThemePickerState
}
proton.ThemePicker(ctx, &u.picker, a)Load once at startup, draw anywhere.
//go:embed assets/logo.png
var logoBytes []byte
func main() {
a := proton.New("myapp")
a.SetLogoBytes(logoBytes)
a.Window("My App", 480, 300, func(ctx proton.Context) {
proton.Row(ctx,
func(ctx proton.Context) { proton.Logo(ctx, 40, 40) },
func(ctx proton.Context) { proton.Gap(ctx, 10) },
func(ctx proton.Context) { proton.H5(ctx, "My App") },
)
})
a.Run()
}Or load from a file path:
a.SetLogo("assets/logo.png")PNG and JPEG both work. The image is decoded once and cached — not re-read per frame.
proton.Alert(ctx, proton.AlertInfo, "Informational message.")
proton.Alert(ctx, proton.AlertSuccess, "Operation completed.")
proton.Alert(ctx, proton.AlertWarning, "Proceed with caution.")
proton.Alert(ctx, proton.AlertError, "Something went wrong.")
// dismissable
if proton.AlertDismissable(ctx, &u.closeBtn, proton.AlertInfo, "Click x to close") {
u.showAlert = false
}
// toast — call last in your draw function
u.toast.Show("Saved!", 2*time.Second)
proton.Toast(ctx, &u.toast)Table
proton.Table(ctx,
[]string{"Name", "Status", "Score"},
[]proton.TableRow{
{"Alice", "Active", "98"},
{"Bob", "Away", "74"},
},
)Stepper
proton.Stepper(ctx, currentStep, []string{"Build", "Test", "Stage", "Deploy"})ProgressRing
proton.ProgressRing(ctx, 0.72, 48, 5, proton.RGB(0x88c0d0))SearchInput
q := proton.SearchInput(ctx, &u.search, "Search notes...")Avatar
proton.Avatar(ctx, "AJ", proton.RGB(0x5e81ac), proton.RGB(0xeceff4), 40)NumberInput
qty := proton.NumberInput(ctx, &u.qty, 1, 99, 1)Overlay / modal
proton.Overlay(ctx, &u.modal, func(ctx proton.Context) {
proton.Card(ctx, proton.RGB(0x2e3440), 12, 24, func(ctx proton.Context) {
proton.H5(ctx, "Confirm?")
proton.Gap(ctx, 16)
proton.Pad(ctx, 4, func(ctx proton.Context) {
if proton.Button(ctx, &u.closeBtn, "Close") {
u.modal.Hide()
}
})
})
})go func() {
result := fetchFromAPI()
u.data = result
ctx.Invalidate()
}()proton.OnKey(ctx, proton.ModCtrl, "S", func() { save() })
proton.OnKey(ctx, proton.ModNone, proton.KeyEscape, func() { closeDialog() })
proton.OnKey(ctx, proton.ModCtrl|proton.ModShift, "N", func() { newWindow() })a.WindowEx("App", 800, 600, []proton.WindowOption{
proton.Fullscreen(),
}, draw)Same code runs on Android. No rewrites.
go install gioui.org/cmd/gogio@latest
gogio -target android -appid com.yourname.yourapp .
adb install yourapp.apkFull guide: docs/10-android.md
Every draw function takes proton.Context — an interface. No Gio types appear in the public API. If Gio's internals change in a future version, only Proton's implementation updates. Your code keeps compiling unchanged.
func (app *MyApp) draw(ctx proton.Context) {
proton.Button(ctx, &app.btn, "Click")
}go run ./examples/hello # minimal — one window, one label
go run ./examples/todo # todo list
go run ./examples/calculator # grid of buttons
go run ./examples/notes # note-taking app with search and split pane
go run ./examples/dashboard # dev dashboard with charts, logs, and tables
go run ./examples/showcase # every widget in one place
go run ./examples/themes # live theme picker
go run ./examples/logoapp # custom logo with go:embed
go run ./examples/kitchen # stress test for all featuresMIT








