---
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
```
```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
```
```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).