Window Basics
Learn the fundamentals of window management.
Wails provides frameless window support with CSS-based drag regions and platform-native behaviour. Remove the platform-native title bar for complete control over window chrome, custom designs, and unique user experiences whilst maintaining essential functionality like dragging, resizing, and system controls.
window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Frameless App", Width: 800, Height: 600, Frameless: true,})CSS for draggable title bar:
.titlebar { --wails-draggable: drag; height: 40px; background: #333;}
.titlebar button { --wails-draggable: no-drag;}HTML:
<div class="titlebar"> <span>My Application</span> <button onclick="window.close()">×</button></div>That’s it! You have a custom title bar.
window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, Width: 800, Height: 600,})What you get:
What you need to implement:
window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, BackgroundType: application.BackgroundTypeTransparent,})Use cases:
Use the --wails-draggable CSS property:
/* Draggable area */.titlebar { --wails-draggable: drag;}
/* Non-draggable elements within draggable area */.titlebar button { --wails-draggable: no-drag;}Values:
drag - Area is draggableno-drag - Area is not draggable (even if parent is)<div class="titlebar"> <div class="title">My Application</div> <div class="controls"> <button class="minimize">−</button> <button class="maximize">□</button> <button class="close">×</button> </div></div>.titlebar { --wails-draggable: drag; display: flex; justify-content: space-between; align-items: center; height: 40px; background: #2c2c2c; color: white; padding: 0 16px;}
.title { font-size: 14px; user-select: none;}
.controls { display: flex; gap: 8px;}
.controls button { --wails-draggable: no-drag; width: 32px; height: 32px; border: none; background: transparent; color: white; font-size: 16px; cursor: pointer; border-radius: 4px;}
.controls button:hover { background: rgba(255, 255, 255, 0.1);}
.controls .close:hover { background: #e81123;}JavaScript for buttons:
import { Window } from '@wailsio/runtime'
document.querySelector('.minimize').addEventListener('click', () => Window.Minimise())document.querySelector('.maximize').addEventListener('click', () => Window.Maximise())document.querySelector('.close').addEventListener('click', () => Window.Close())Go side:
type WindowControls struct { window *application.WebviewWindow}
func (wc *WindowControls) Minimise() { wc.window.Minimise()}
func (wc *WindowControls) Maximise() { if wc.window.IsMaximised() { wc.window.UnMaximise() } else { wc.window.Maximise() }}
func (wc *WindowControls) Close() { wc.window.Close()}JavaScript side:
import { Minimise, Maximise, Close } from './bindings/WindowControls'
document.querySelector('.minimize').addEventListener('click', Minimise)document.querySelector('.maximize').addEventListener('click', Maximise)document.querySelector('.close').addEventListener('click', Close)Or use runtime methods:
import { Window } from '@wailsio/runtime'
document.querySelector('.minimize').addEventListener('click', () => Window.Minimise())document.querySelector('.maximize').addEventListener('click', () => Window.Maximise())document.querySelector('.close').addEventListener('click', () => Window.Close())Track maximise state for button icon:
import { Window } from '@wailsio/runtime'
async function toggleMaximise() { const isMaximised = await Window.IsMaximised()
if (isMaximised) { await Window.Restore() } else { await Window.Maximise() }
updateMaximiseButton()}
async function updateMaximiseButton() { const isMaximised = await Window.IsMaximised() const button = document.querySelector('.maximize') button.textContent = isMaximised ? '❐' : '□'}Wails provides automatic resize handles for frameless windows:
/* Enable resize on all edges */body { --wails-resize: all;}
/* Or specific edges */.resize-top { --wails-resize: top;}
.resize-bottom { --wails-resize: bottom;}
.resize-left { --wails-resize: left;}
.resize-right { --wails-resize: right;}
/* Corners */.resize-top-left { --wails-resize: top-left;}
.resize-top-right { --wails-resize: top-right;}
.resize-bottom-left { --wails-resize: bottom-left;}
.resize-bottom-right { --wails-resize: bottom-right;}Values:
all - Resize from all edgestop, bottom, left, right - Specific edgestop-left, top-right, bottom-left, bottom-right - Cornersnone - No resize<div class="window"> <div class="titlebar">...</div> <div class="content">...</div> <div class="resize-handle resize-bottom-right"></div></div>.resize-handle { position: absolute; width: 16px; height: 16px;}
.resize-bottom-right { --wails-resize: bottom-right; bottom: 0; right: 0; cursor: nwse-resize;}Windows frameless windows:
window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, Windows: application.WindowsOptions{ DisableFramelessWindowDecorations: false, },})Features:
Disable decorations:
Windows: application.WindowsOptions{ DisableFramelessWindowDecorations: true,},Snap Assist:
// Trigger Windows 11 Snap Assistwindow.SnapAssist()Custom title bar height: Windows automatically detects drag regions from CSS.
macOS frameless windows:
window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, Mac: application.MacOptions{ TitleBarAppearsTransparent: true, InvisibleTitleBarHeight: 40, },})Features:
Hide traffic lights:
Mac: application.MacOptions{ TitleBarStyle: application.MacTitleBarStyleHidden,},Invisible title bar:
Allows dragging whilst hiding the title bar. This only takes effect when the window is frameless or uses AppearsTransparent:
Mac: application.MacOptions{ InvisibleTitleBarHeight: 40,},Linux frameless windows:
window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true,})Features:
Desktop environment notes:
Compositor required: Transparency requires a compositor (most modern DEs have one).
<div class="modern-titlebar"> <div class="app-icon"> <img src="/icon.png" alt="App Icon"> </div> <div class="title">My Application</div> <div class="controls"> <button class="minimize">−</button> <button class="maximize">□</button> <button class="close">×</button> </div></div>.modern-titlebar { --wails-draggable: drag; display: flex; align-items: center; height: 40px; background: linear-gradient(to bottom, #3a3a3a, #2c2c2c); border-bottom: 1px solid #1a1a1a; padding: 0 16px;}
.app-icon { --wails-draggable: no-drag; width: 24px; height: 24px; margin-right: 12px;}
.title { flex: 1; font-size: 13px; color: #e0e0e0; user-select: none;}
.controls { display: flex; gap: 1px;}
.controls button { --wails-draggable: no-drag; width: 46px; height: 32px; border: none; background: transparent; color: #e0e0e0; font-size: 14px; cursor: pointer; transition: background 0.2s;}
.controls button:hover { background: rgba(255, 255, 255, 0.1);}
.controls .close:hover { background: #e81123; color: white;}splash := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Loading...", Width: 400, Height: 300, Frameless: true, AlwaysOnTop: true, BackgroundType: application.BackgroundTypeTransparent,})body { background: transparent; display: flex; justify-content: center; align-items: center;}
.splash { background: white; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); padding: 40px; text-align: center;}window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, BackgroundType: application.BackgroundTypeTransparent,})body { background: transparent; margin: 8px;}
.window { background: white; border-radius: 16px; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15); overflow: hidden; height: calc(100vh - 16px);}
.titlebar { --wails-draggable: drag; background: #f5f5f5; border-bottom: 1px solid #e0e0e0;}overlay := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, AlwaysOnTop: true, BackgroundType: application.BackgroundTypeTransparent,})body { background: transparent;}
.overlay { background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(10px); border-radius: 8px; padding: 20px;}Here’s a production-ready frameless window:
Go:
package main
import ( _ "embed" "github.com/wailsapp/wails/v3/pkg/application")
//go:embed frontend/distvar assets embed.FS
func main() { app := application.New(application.Options{ Name: "Frameless App", })
window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Frameless Application", Width: 1000, Height: 700, MinWidth: 800, MinHeight: 600, Frameless: true,
Assets: application.AssetOptions{ Handler: application.AssetFileServerFS(assets), },
Mac: application.MacOptions{ TitleBarAppearsTransparent: true, InvisibleTitleBarHeight: 40, },
Windows: application.WindowsOptions{ DisableFramelessWindowDecorations: false, }, })
window.Center() window.Show()
app.Run()}HTML:
<!DOCTYPE html><html><head> <link rel="stylesheet" href="/style.css"></head><body> <div class="window"> <div class="titlebar"> <div class="title">Frameless Application</div> <div class="controls"> <button class="minimize" title="Minimise">−</button> <button class="maximize" title="Maximise">□</button> <button class="close" title="Close">×</button> </div> </div> <div class="content"> <h1>Hello from Frameless Window!</h1> </div> </div> <script src="/main.js" type="module"></script></body></html>CSS:
* { margin: 0; padding: 0; box-sizing: border-box;}
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5;}
.window { height: 100vh; display: flex; flex-direction: column;}
.titlebar { --wails-draggable: drag; display: flex; justify-content: space-between; align-items: center; height: 40px; background: #ffffff; border-bottom: 1px solid #e0e0e0; padding: 0 16px;}
.title { font-size: 13px; font-weight: 500; color: #333; user-select: none;}
.controls { display: flex; gap: 8px;}
.controls button { --wails-draggable: no-drag; width: 32px; height: 32px; border: none; background: transparent; color: #666; font-size: 16px; cursor: pointer; border-radius: 4px; display: flex; align-items: center; justify-content: center; transition: all 0.2s;}
.controls button:hover { background: #f0f0f0; color: #333;}
.controls .close:hover { background: #e81123; color: white;}
.content { flex: 1; padding: 40px; overflow: auto;}JavaScript:
import { Window } from '@wailsio/runtime'
// Minimise buttondocument.querySelector('.minimize').addEventListener('click', () => { Window.Minimise()})
// Maximise/restore buttonconst maximiseBtn = document.querySelector('.maximize')maximiseBtn.addEventListener('click', async () => { const isMaximised = await Window.IsMaximised()
if (isMaximised) { await Window.Restore() } else { await Window.Maximise() }
updateMaximiseButton()})
// Close buttondocument.querySelector('.close').addEventListener('click', () => { Window.Close()})
// Update maximise button iconasync function updateMaximiseButton() { const isMaximised = await Window.IsMaximised() maximiseBtn.textContent = isMaximised ? '❐' : '□' maximiseBtn.title = isMaximised ? 'Restore' : 'Maximise'}
// Initial stateupdateMaximiseButton()Cause: Missing --wails-draggable: drag
Solution:
.titlebar { --wails-draggable: drag;}Cause: Buttons are in draggable area
Solution:
.titlebar button { --wails-draggable: no-drag;}Cause: Missing resize handles
Solution:
body { --wails-resize: all;}Window Basics
Learn the fundamentals of window management.
Window Options
Complete reference for window options.
Window Events
Handle window lifecycle events.
Multiple Windows
Patterns for multi-window applications.
Questions? Ask in Discord or check the frameless example.