gui/docs/ref/wails-v3/features/windows/frameless.mdx
Snider 4bdbb68f46
Some checks failed
Security Scan / security (push) Failing after 9s
Test / test (push) Failing after 1m21s
refactor: update import path from go-config to core/config
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-14 10:26:36 +00:00

860 lines
17 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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
<div class="titlebar">
<span>My Application</span>
<button onclick="window.close()">×</button>
</div>
```
**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
<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>
```
```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
<div class="window">
<div class="titlebar">...</div>
<div class="content">...</div>
<div class="resize-handle resize-bottom-right"></div>
</div>
```
```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
<Tabs syncKey="platform">
<TabItem label="Windows" icon="seti:windows">
**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.
</TabItem>
<TabItem label="macOS" icon="apple">
**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,
},
```
</TabItem>
<TabItem label="Linux" icon="linux">
**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).
</TabItem>
</Tabs>
## Common Patterns
### Pattern 1: Modern Title Bar
```html
<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>
```
```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
<!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:**
```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
<CardGrid>
<Card title="Window Basics" icon="laptop">
Learn the fundamentals of window management.
[Learn More →](/features/windows/basics)
</Card>
<Card title="Window Options" icon="seti:config">
Complete reference for window options.
[Learn More →](/features/windows/options)
</Card>
<Card title="Window Events" icon="rocket">
Handle window lifecycle events.
[Learn More →](/features/windows/events)
</Card>
<Card title="Multiple Windows" icon="puzzle">
Patterns for multi-window applications.
[Learn More →](/features/windows/multiple)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [frameless example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/frameless).