README.md

April 30, 2026 · View on GitHub

GoGPU Logo

systray

Pure Go system tray library for Windows, macOS, and Linux
Zero CGO. Cross-platform. Multiple trays. Context menus. Notifications.

CI Coverage Go Reference Go Report Card License Zero CGO


Features

  • Pure Go — zero CGO on all platforms. Single binary, easy cross-compilation
  • Multiple trays — create as many tray icons as you need
  • Context menus — nested menus with checkboxes, separators, icons, and submenus
  • Notifications — balloon tips (Windows), notification center (macOS), D-Bus notifications (Linux)
  • Dark mode — automatic icon switching for light/dark themes (Windows)
  • Template icons — macOS-native monochrome icons that adapt to system theme
  • Builder pattern — fluent API for clean, readable code
  • Message loop — built-in Run() blocks and pumps the platform event loop
  • Standalone — no dependency on gogpu framework. Use in any Go application

Platform Implementation

PlatformAPIDependencyStatus
WindowsShell_NotifyIconW (shell32.dll)golang.org/x/sys/windowsImplemented
macOSNSStatusBar / NSStatusItem (AppKit)github.com/go-webgpu/goffiImplemented
LinuxStatusNotifierItem (D-Bus SNI)github.com/godbus/dbus/v5Implemented

All platform implementations use Pure Go FFI — no C compiler required.

Installation

go get github.com/gogpu/systray

Requirements: Go 1.25+

Quick Start

package main

import (
    "fmt"
    "os"

    "github.com/gogpu/systray"
)

func main() {
    tray := systray.New()

    // Build context menu
    menu := systray.NewMenu()
    menu.Add("Hello", func() { fmt.Println("Hello clicked!") })
    menu.Add("Show Notification", func() {
        tray.ShowNotification("My App", "Hello from systray!")
    })
    menu.AddSeparator()
    menu.AddCheckbox("Check me", false, func() { fmt.Println("Toggled") })
    menu.AddSeparator()
    menu.Add("Quit", func() {
        tray.Remove()
        os.Exit(0)
    })

    // Configure and show
    tray.SetIcon(iconPNG).
        SetTooltip("My Application").
        SetMenu(menu)
    tray.OnClick(func() { fmt.Println("Left click!") })
    tray.Show()

    // Run the platform message loop (blocks until Quit)
    if err := tray.Run(); err != nil {
        fmt.Println("error:", err)
    }
}

API Reference

SystemTray

// Create and lifecycle
tray := systray.New()              // Create a new system tray icon
tray.Show()                        // Show tray icon
tray.Hide()                        // Hide tray icon (without removing)
tray.Run()                         // Block and pump the platform message loop
tray.Remove()                      // Destroy tray icon and release resources

// Icon management
tray.SetIcon(png []byte)           // Set tray icon (PNG format)
tray.SetDarkModeIcon(png []byte)   // Auto-switch in dark mode (Windows)
tray.SetTemplateIcon(png []byte)   // macOS template image (monochrome)

// Text and menu
tray.SetTooltip(text string)       // Hover tooltip
tray.SetMenu(menu *Menu)           // Attach context menu

// Events
tray.OnClick(fn func())            // Left click handler
tray.OnDoubleClick(fn func())      // Double click handler
tray.OnRightClick(fn func())       // Right click handler

// Notifications
tray.ShowNotification(title, message string)  // OS-level notification

// Position (for window placement near tray)
x, y, w, h := tray.Bounds()       // Tray icon screen position

All setter methods return *SystemTray for fluent chaining:

tray.SetIcon(icon).SetTooltip("Ready").SetMenu(menu).Show()
menu := systray.NewMenu()

menu.Add("Label", onClick)                          // Normal item
menu.AddCheckbox("Toggle", checked, onChange)        // Checkbox item
menu.AddSeparator()                                 // Visual separator
menu.AddSubmenu("More", submenu)                    // Nested submenu
menu.AddWithIcon("Save", iconPNG, onClick)          // Item with icon

All Menu methods return *Menu for chaining.

Multiple Trays

// Each tray is independent with its own icon, menu, and handlers
mainTray := systray.New().SetIcon(appIcon).SetMenu(mainMenu).Show()
statusTray := systray.New().SetIcon(statusIcon).SetTooltip("Status: OK").Show()

Dark Mode

systray supports automatic icon switching based on the system theme.

Windows — Use SetDarkModeIcon() to provide an alternative icon for dark mode. The library detects theme changes via WM_SETTINGCHANGE with "ImmersiveColorSet" and switches icons automatically:

tray.SetIcon(lightIcon).SetDarkModeIcon(darkIcon)

macOS — Use SetTemplateIcon() with a monochrome PNG. macOS renders template images with the correct color for the current menu bar appearance (light or dark). Only the alpha channel matters:

tray.SetTemplateIcon(monochromeIcon)

Linux — The SNI protocol delivers the icon pixmap to the desktop environment, which handles theme adaptation. No special API is needed.

Notifications

ShowNotification sends an OS-level notification from the tray icon:

tray.ShowNotification("Update Available", "Version 2.0 is ready to install.")
PlatformMechanismNotes
WindowsBalloon tip (Shell_NotifyIconW + NIF_INFO)Appears near the tray icon
macOSNSUserNotification / Notification CenterRequires notification permission on macOS 13+
Linuxorg.freedesktop.Notifications D-BusWorks on GNOME, KDE, XFCE, and other FreeDesktop-compliant DEs

Icon Guidelines

PlatformRecommended SizeFormatNotes
Windows16x16, 32x32PNGProvide both sizes for standard and HiDPI
macOS22x22, 44x44 (@2x)PNGMust be monochrome (template) for proper theme adaptation
Linux22x22, 24x24PNGSNI spec recommends 22x22

Input format: PNG bytes ([]byte). The library handles conversion to native format (HICON, NSImage, ARGB pixmap) internally.

For macOS, use SetTemplateIcon() with a monochrome PNG (only alpha channel matters). The system automatically adjusts the icon color for light/dark menu bar.

Architecture

systray.New()  ->  SystemTray (public API)
                       |
                  PlatformTray (internal interface)
                       |
          +------------+------------+
          |            |            |
     Win32 impl   macOS impl   Linux impl
     Shell_Notify  NSStatusBar   D-Bus SNI
     IconW         NSStatusItem  StatusNotifierItem

Follows the Qt6 QPlatformSystemTrayIcon three-layer pattern. Each platform implementation is isolated in its own file with build constraints.

Usage with gogpu

While systray is fully standalone, it integrates seamlessly with the gogpu application framework:

import (
    "github.com/gogpu/gogpu"
    "github.com/gogpu/systray"
)

app := gogpu.NewApp(config)

// Create tray through the app (lifecycle managed automatically)
tray := systray.New()
tray.SetIcon(icon).SetMenu(menu).Show()

// Minimize to tray pattern
app.SetQuitBehavior(gogpu.QuitOnExplicitQuit)
app.OnClose(func() bool {
    app.Hide()       // hide window instead of closing
    return false     // reject close
})
tray.OnClick(func() {
    app.Show()       // restore window on tray click
})

Comparison with Alternatives

Featuregogpu/systraygetlantern/systrayfyne-io/systray
Pure Go (zero CGO)YesNo (CGO on macOS/Linux)No (CGO on macOS/Linux)
Multiple traysYesNo (single global)No (single global)
Dark mode iconsYesNoNo
Template icons (macOS)YesNoYes
Nested menusYesYesYes
Menu item iconsYesNoNo
NotificationsYesNoNo
Builder patternYesNoNo
Built-in message loopYesYesYes
Wayland supportYes (D-Bus SNI)NoPartial

Contributing

We welcome contributions! See CONTRIBUTING.md for guidelines.

Part of the GoGPU Ecosystem

systray is part of the GoGPU ecosystem — 790K+ lines of Pure Go, zero CGO. A GPU computing platform with a WebGPU implementation, shader compiler, 2D graphics library, and GUI toolkit.

LibraryPurpose
gogpuApplication framework, windowing
wgpuPure Go WebGPU (Vulkan/Metal/DX12/GLES)
nagaShader compiler (WGSL to SPIR-V/MSL/GLSL/HLSL/DXIL)
gg2D graphics with GPU acceleration
uiGUI toolkit (22+ widgets, 4 themes)
systraySystem tray (this library)

License

MIT License — see LICENSE for details.