Menu System Tray
Menu System Tray
Wails menyediakan API system tray terpadu yang bekerja di semua platform. Buat ikon tray dengan menu, lampirkan window, dan tangani klik dengan perilaku platform native untuk aplikasi background, service, dan utilitas akses cepat.
Mulai Cepat
package main
import (
_ "embed"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed assets/icon.png
var icon []byte
func main() {
app := application.New(application.Options{
Name: "Tray App",
})
// Create system tray
systray := app.SystemTray.New()
systray.SetIcon(icon)
systray.SetLabel("My App")
// Add menu
menu := app.NewMenu()
menu.Add("Show").OnClick(func(ctx *application.Context) {
// Show main window
})
menu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
systray.SetMenu(menu)
// Create hidden window
window := app.Window.New()
window.Hide()
app.Run()
}Result: System tray icon with menu on all platforms.
Membuat System Tray
Basic System Tray
// Create system tray
systray := app.SystemTray.New()
// Set icon
systray.SetIcon(iconBytes)
// Set label (macOS) / tooltip (Windows)
systray.SetLabel("My Application")With Icon
Icons should be embedded:
import _ "embed"
//go:embed assets/icon.png
var icon []byte
//go:embed assets/icon-dark.png
var iconDark []byte
func main() {
app := application.New(application.Options{
Name: "My App",
})
systray := app.SystemTray.New()
systray.SetIcon(icon)
systray.SetDarkModeIcon(iconDark) // macOS dark mode
app.Run()
}Icon requirements:
| Platform | Size | Format | Notes |
|---|---|---|---|
| Windows | 16x16 or 32x32 | PNG, ICO | Notification area |
| macOS | 18x18 to 22x22 | PNG | Menu bar, template recommended |
| Linux | 22x22 to 48x48 | PNG, SVG | Varies by DE |
Template Icons (macOS)
Template icons adapt to light/dark mode automatically:
systray.SetTemplateIcon(iconBytes)Template icon guidelines:
- Use black and clear (transparent) colours only
- Black becomes white in dark mode
- Name file with
Templatesuffix:iconTemplate.png - Design guide
Menambahkan Menu
System tray menus work like application menus:
menu := app.NewMenu()
// Add items
menu.Add("Open").OnClick(func(ctx *application.Context) {
showMainWindow()
})
menu.AddSeparator()
menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) {
enabled := ctx.ClickedMenuItem().Checked()
setStartAtLogin(enabled)
})
menu.AddSeparator()
menu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
// Set menu
systray.SetMenu(menu)For all menu item types, see Menu Reference.
Melampirkan Window
Attach a window to the tray icon for automatic show/hide:
// Create window
window := app.Window.New()
// Attach to tray
systray.AttachWindow(window)
// Configure behaviour — these are setters that return the receiver for chaining.
systray.WindowOffset(10) // Pixels from tray icon
systray.WindowDebounce(200 * time.Millisecond) // Click debounceBehaviour:
- Window starts hidden
- Left-click tray icon → Toggle window visibility
- Right-click tray icon → Show menu (if set)
- Window positioned near tray icon
Example: Popup window
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Quick Access",
Width: 300,
Height: 400,
Frameless: true, // No title bar
AlwaysOnTop: true, // Stay on top
})
systray.AttachWindow(window)
systray.WindowOffset(5)Handler Klik
Handle tray icon clicks:
systray := app.SystemTray.New()
// Left click
systray.OnClick(func() {
fmt.Println("Tray icon clicked")
})
// Right click
systray.OnRightClick(func() {
fmt.Println("Tray icon right-clicked")
})
// Double click
systray.OnDoubleClick(func() {
fmt.Println("Tray icon double-clicked")
})
// Mouse enter/leave
systray.OnMouseEnter(func() {
fmt.Println("Mouse entered tray icon")
})
systray.OnMouseLeave(func() {
fmt.Println("Mouse left tray icon")
})Platform support:
| Event | Windows | macOS | Linux |
|---|---|---|---|
| OnClick | ✅ | ✅ | ✅ |
| OnRightClick | ✅ | ✅ | ✅ |
| OnDoubleClick | ✅ | ✅ | ⚠️ Varies |
| OnMouseEnter | ✅ | ✅ | ⚠️ Varies |
| OnMouseLeave | ✅ | ✅ | ⚠️ Varies |
Pembaruan Dinamis
Update tray icon and menu dynamically:
Change Icon
var isActive bool
func updateTrayIcon() {
if isActive {
systray.SetIcon(activeIcon)
systray.SetLabel("Active")
} else {
systray.SetIcon(inactiveIcon)
systray.SetLabel("Inactive")
}
}Update Menu
var isPaused bool
pauseMenuItem := menu.Add("Pause")
pauseMenuItem.OnClick(func(ctx *application.Context) {
isPaused = !isPaused
if isPaused {
pauseMenuItem.SetLabel("Resume")
} else {
pauseMenuItem.SetLabel("Pause")
}
menu.Update() // Important!
})Rebuild Menu
For major changes, rebuild the entire menu:
func rebuildTrayMenu(status string) {
menu := app.NewMenu()
// Status-specific items
switch status {
case "syncing":
menu.Add("Syncing...").SetEnabled(false)
menu.Add("Pause Sync").OnClick(pauseSync)
case "synced":
menu.Add("Up to date ✓").SetEnabled(false)
menu.Add("Sync Now").OnClick(startSync)
case "error":
menu.Add("Sync Error").SetEnabled(false)
menu.Add("Retry").OnClick(retrySync)
}
menu.AddSeparator()
menu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
systray.SetMenu(menu)
}Platform-Specific Features
Menu bar integration:
// Set label (appears next to icon)
systray.SetLabel("My App")
// Use template icon (adapts to dark mode)
systray.SetTemplateIcon(iconBytes)
// Set icon position — uses AppKit NSImage placement constants.
systray.SetIconPosition(application.NSImageRight)Icon positions (mirroring NSImagePosition):
application.NSImageLeft- Icon left of label.application.NSImageRight- Icon right of label.application.NSImageOnly- Icon only, no label.application.NSImageNone- Label only, no icon.
Best practices:
- Use template icons (black + transparent)
- Keep labels short (3-5 characters)
- 18x18 to 22x22 pixels for Retina displays
- Test in both light and dark modes
Notification area integration:
// Set tooltip (appears on hover)
systray.SetTooltip("My Application")
// Or use SetLabel (same as tooltip on Windows)
systray.SetLabel("My Application")
// Show/Hide functionality (fully functional)
systray.Show() // Show tray icon
systray.Hide() // Hide tray iconIcon requirements:
- 16x16 or 32x32 pixels
- PNG or ICO format
- Transparent background
Tooltip limits:
- Maximum 127 UTF-16 characters
- Longer tooltips will be truncated
- Keep concise for best experience
Platform features:
- Tray icon survives Windows Explorer restarts
- Show() and Hide() methods fully functional
- Proper lifecycle management
Best practices:
- Use 32x32 for high-DPI displays
- Keep tooltips under 127 characters
- Test on different Windows versions
- Consider notification area overflow
- Use Show/Hide for conditional tray visibility
System tray integration:
Uses StatusNotifierItem specification (most modern DEs).
systray.SetIcon(iconBytes)
systray.SetLabel("My App")Desktop environment support:
- GNOME: Top bar (with extension)
- KDE Plasma: System tray
- XFCE: Notification area
- Others: Varies
Best practices:
- Use 22x22 or 24x24 pixels
- SVG icons scale better
- Test on target desktop environments
- Provide fallback for unsupported DEs
Contoh Lengkap
Here’s a production-ready system tray application:
package main
import (
_ "embed"
"fmt"
"time"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed assets/icon.png
var icon []byte
//go:embed assets/icon-active.png
var iconActive []byte
type TrayApp struct {
app *application.App
systray *application.SystemTray
window *application.WebviewWindow
menu *application.Menu
isActive bool
}
func main() {
app := application.New(application.Options{
Name: "Tray Application",
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: false,
},
})
trayApp := &TrayApp{app: app}
trayApp.setup()
app.Run()
}
func (t *TrayApp) setup() {
// Create system tray
t.systray = t.app.SystemTray.New()
t.systray.SetIcon(icon)
t.systray.SetLabel("Inactive")
// Create menu
t.createMenu()
// Create window (hidden by default)
t.window = t.app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Tray Application",
Width: 400,
Height: 600,
Hidden: true,
})
// Attach window to tray
t.systray.AttachWindow(t.window)
t.systray.WindowOffset(10)
// Handle tray clicks
t.systray.OnRightClick(func() {
t.systray.OpenMenu()
})
// Start background task
go t.backgroundTask()
}
func (t *TrayApp) createMenu() {
t.menu = t.app.NewMenu()
// Status item (disabled)
statusItem := t.menu.Add("Status: Inactive")
statusItem.SetEnabled(false)
t.menu.AddSeparator()
// Toggle active
t.menu.Add("Start").OnClick(func(ctx *application.Context) {
t.toggleActive()
})
// Show window
t.menu.Add("Show Window").OnClick(func(ctx *application.Context) {
t.window.Show()
t.window.Focus()
})
t.menu.AddSeparator()
// Settings
t.menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) {
enabled := ctx.ClickedMenuItem().Checked()
t.setStartAtLogin(enabled)
})
t.menu.AddSeparator()
// Quit
t.menu.Add("Quit").OnClick(func(ctx *application.Context) {
t.app.Quit()
})
t.systray.SetMenu(t.menu)
}
func (t *TrayApp) toggleActive() {
t.isActive = !t.isActive
t.updateTray()
}
func (t *TrayApp) updateTray() {
if t.isActive {
t.systray.SetIcon(iconActive)
t.systray.SetLabel("Active")
} else {
t.systray.SetIcon(icon)
t.systray.SetLabel("Inactive")
}
// Rebuild menu with new status
t.createMenu()
}
func (t *TrayApp) backgroundTask() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
if t.isActive {
fmt.Println("Background task running...")
// Do work
}
}
}
func (t *TrayApp) setStartAtLogin(enabled bool) {
// Implementation varies by platform
fmt.Printf("Start at login: %v\n", enabled)
}Kontrol Visibilitas
Show/hide the tray icon dynamically:
// Hide tray icon
systray.Hide()
// Show tray icon
systray.Show()There is no IsVisible() getter — track visibility in your own application state if you need it.
Platform Support:
| Platform | Hide() | Show() | Notes |
|---|---|---|---|
| Windows | ✅ | ✅ | Fully functional - icon appears/disappears from notification area |
| macOS | ✅ | ✅ | Menu bar item shows/hides |
| Linux | ✅ | ✅ | Varies by desktop environment |
Kasus penggunaans:
- Temporarily hide tray icon based on user preference
- Headless mode with tray icon appearing only when needed
- Toggle visibility based on application state
Example - Conditional Tray Visibility:
func (t *TrayApp) setTrayVisibility(visible bool) {
if visible {
t.systray.Show()
} else {
t.systray.Hide()
}
}
// Show tray only when updates are available
func (t *TrayApp) checkForUpdates() {
if hasUpdates {
t.systray.Show()
t.systray.SetLabel("Update Available")
} else {
t.systray.Hide()
}
}Pembersihan
Destroy the tray icon when done:
// In OnShutdown
app := application.New(application.Options{
OnShutdown: func() {
if systray != nil {
systray.Destroy()
}
},
})Important: Always destroy system tray on shutdown to release resources.
Praktik Terbaik
✅ Lakukan
- Use template icons on macOS - Adapts to dark mode
- Keep labels short - 3-5 characters maximum
- Provide tooltips on Windows - Helps users identify your app
- Test on all platforms - Behaviour varies
- Handle clicks appropriately - Left-click for main action, right-click for menu
- Update icon for status - Visual feedback is important
- Destroy on shutdown - Release resources
❌ Jangan
- Don’t use large icons - Follow platform guidelines
- Don’t use long labels - Gets truncated
- Don’t forget dark mode - Test on macOS dark mode
- Don’t block click handlers - Keep them fast
- Don’t forget menu.Update() - After changing menu state
- Don’t assume tray support - Some Linux DEs don’t support it
Troubleshooting
Tray Icon Not Appearing
Possible causes:
- Icon format not supported
- Icon size too large/small
- System tray not supported (Linux)
Solution:
There is no SystemTraySupported() helper; instead create the tray, check the platform, and degrade gracefully:
// Probe support: on Linux without a notification-area extension, the tray
// will simply not appear. Defensive code can fall back to window-only mode
// based on runtime.GOOS or after a short timeout if no tray events arrive.
systray := app.SystemTray.New()
systray.SetIcon(iconBytes)Icon Looks Wrong on macOS
Cause: Not using template icon
Solution:
// Use template icon
systray.SetTemplateIcon(iconBytes)
// Or design icon as template (black + transparent)Menu Not Updating
Cause: Forgot to call menu.Update()
Solution:
menuItem.SetLabel("New Label")
menu.Update() // Add this!Langkah Selanjutnya
Complete reference for menu item types and properties.
Create application menu bars.
Create right-click context menus.
Build a complete system tray application.
Pertanyaan? Tanyakan di Discord or lihat system tray examples.