--- title: Frameless Windows description: Create custom window chrome with frameless windows sidebar: order: 4 --- import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; ## Frameless Windows 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. ## Quick Start ```go window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Frameless App", Width: 800, Height: 600, Frameless: true, }) ``` **CSS for draggable title bar:** ```css .titlebar { --wails-draggable: drag; height: 40px; background: #333; } .titlebar button { --wails-draggable: no-drag; } ``` **HTML:** ```html
My Application
``` **That's it!** You have a custom title bar. ## Creating Frameless Windows ### Basic Frameless Window ```go window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, Width: 800, Height: 600, }) ``` **What you get:** - No title bar - No window borders - No system buttons - Transparent background (optional) **What you need to implement:** - Draggable area - Close/minimise/maximise buttons - Resize handles (if resizable) ### With Transparent Background ```go window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, BackgroundType: application.BackgroundTypeTransparent, }) ``` **Use cases:** - Rounded corners - Custom shapes - Overlay windows - Splash screens ## Drag Regions ### CSS-Based Dragging Use the `--wails-draggable` CSS property: ```css /* Draggable area */ .titlebar { --wails-draggable: drag; } /* Non-draggable elements within draggable area */ .titlebar button { --wails-draggable: no-drag; } ``` **Values:** - `drag` - Area is draggable - `no-drag` - Area is not draggable (even if parent is) ### Complete Title Bar Example ```html
My Application
``` ```css .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:** ```javascript 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()) ``` ## System Buttons ### Implementing Close/Minimise/Maximise **Go side:** ```go 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:** ```javascript 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:** ```javascript 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()) ``` ### Toggle Maximise State Track maximise state for button icon: ```javascript 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 ? '❐' : '□' } ``` ## Resize Handles ### CSS-Based Resize Wails provides automatic resize handles for frameless windows: ```css /* 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 edges - `top`, `bottom`, `left`, `right` - Specific edges - `top-left`, `top-right`, `bottom-left`, `bottom-right` - Corners - `none` - No resize ### Resize Handle Example ```html
...
...
``` ```css .resize-handle { position: absolute; width: 16px; height: 16px; } .resize-bottom-right { --wails-resize: bottom-right; bottom: 0; right: 0; cursor: nwse-resize; } ``` ## Platform-Specific Behaviour **Windows frameless windows:** ```go window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, Windows: application.WindowsOptions{ DisableFramelessWindowDecorations: false, }, }) ``` **Features:** - Automatic drop shadow - Snap layouts support (Windows 11) - Aero Snap support - DPI scaling **Disable decorations:** ```go Windows: application.WindowsOptions{ DisableFramelessWindowDecorations: true, }, ``` **Snap Assist:** ```go // Trigger Windows 11 Snap Assist window.SnapAssist() ``` **Custom title bar height:** Windows automatically detects drag regions from CSS. **macOS frameless windows:** ```go window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, Mac: application.MacOptions{ TitleBarAppearsTransparent: true, InvisibleTitleBarHeight: 40, }, }) ``` **Features:** - Native fullscreen support - Traffic light buttons (optional) - Vibrancy effects - Transparent title bar **Hide traffic lights:** ```go 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`: ```go Mac: application.MacOptions{ InvisibleTitleBarHeight: 40, }, ``` **Linux frameless windows:** ```go window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, }) ``` **Features:** - Basic frameless support - CSS drag regions - Varies by desktop environment **Desktop environment notes:** - **GNOME:** Good support - **KDE Plasma:** Good support - **XFCE:** Basic support - **Tiling WMs:** Limited support **Compositor required:** Transparency requires a compositor (most modern DEs have one). ## Common Patterns ### Pattern 1: Modern Title Bar ```html
App Icon
My Application
``` ```css .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; } ``` ### Pattern 2: Splash Screen ```go splash := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Loading...", Width: 400, Height: 300, Frameless: true, AlwaysOnTop: true, BackgroundType: application.BackgroundTypeTransparent, }) ``` ```css 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; } ``` ### Pattern 3: Rounded Window ```go window := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, BackgroundType: application.BackgroundTypeTransparent, }) ``` ```css 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; } ``` ### Pattern 4: Overlay Window ```go overlay := app.Window.NewWithOptions(application.WebviewWindowOptions{ Frameless: true, AlwaysOnTop: true, BackgroundType: application.BackgroundTypeTransparent, }) ``` ```css body { background: transparent; } .overlay { background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(10px); border-radius: 8px; padding: 20px; } ``` ## Complete Example Here's a production-ready frameless window: **Go:** ```go package main import ( _ "embed" "github.com/wailsapp/wails/v3/pkg/application" ) //go:embed frontend/dist var 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:** ```html
Frameless Application

Hello from Frameless Window!

``` **CSS:** ```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:** ```javascript import { Window } from '@wailsio/runtime' // Minimise button document.querySelector('.minimize').addEventListener('click', () => { Window.Minimise() }) // Maximise/restore button const maximiseBtn = document.querySelector('.maximize') maximiseBtn.addEventListener('click', async () => { const isMaximised = await Window.IsMaximised() if (isMaximised) { await Window.Restore() } else { await Window.Maximise() } updateMaximiseButton() }) // Close button document.querySelector('.close').addEventListener('click', () => { Window.Close() }) // Update maximise button icon async function updateMaximiseButton() { const isMaximised = await Window.IsMaximised() maximiseBtn.textContent = isMaximised ? '❐' : '□' maximiseBtn.title = isMaximised ? 'Restore' : 'Maximise' } // Initial state updateMaximiseButton() ``` ## Best Practices ### ✅ Do - **Provide draggable area** - Users need to move the window - **Implement system buttons** - Close, minimise, maximise - **Set minimum size** - Prevent unusable layouts - **Test on all platforms** - Behaviour varies - **Use CSS for drag regions** - Flexible and maintainable - **Provide visual feedback** - Hover states on buttons ### ❌ Don't - **Don't forget resize handles** - If window is resizable - **Don't make entire window draggable** - Prevents interaction - **Don't forget no-drag on buttons** - They won't work - **Don't use tiny drag areas** - Hard to grab - **Don't forget platform differences** - Test thoroughly ## Troubleshooting ### Window Won't Drag **Cause:** Missing `--wails-draggable: drag` **Solution:** ```css .titlebar { --wails-draggable: drag; } ``` ### Buttons Don't Work **Cause:** Buttons are in draggable area **Solution:** ```css .titlebar button { --wails-draggable: no-drag; } ``` ### Can't Resize Window **Cause:** Missing resize handles **Solution:** ```css body { --wails-resize: all; } ``` ## Next Steps Learn the fundamentals of window management. [Learn More →](/features/windows/basics) Complete reference for window options. [Learn More →](/features/windows/options) Handle window lifecycle events. [Learn More →](/features/windows/events) Patterns for multi-window applications. [Learn More →](/features/windows/multiple) --- **Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [frameless example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/frameless).