Custom titlebar
--wails-draggable + Tailwind.
A frameless Wails window has no OS titlebar — you draw your own. Pros: it looks like Linear / Discord / VS Code, not Win32. Cons: you have to wire drag-to-move yourself. This lesson covers both.
Enable frameless in main.go
wails.Run(&options.App{Title: "Field POS",Width: 1280,Height: 800,Frameless: true, // ← removes OS titlebarCSSDragProperty: "--wails-draggable",CSSDragValue: "drag",// ...})
Frameless + the CSS drag declarations let you mark which HTML elements can drag the window. Anything with style="--wails-draggable: drag" becomes a drag handle.
The titlebar component
import { Quit, WindowMinimise, WindowToggleMaximise } from '../../wailsjs/runtime/runtime'import { Minus, Square, X } from 'lucide-react'export function TitleBar() {return (<divstyle={{ '--wails-draggable': 'drag' } as any}className="h-10 flex items-center justify-between px-4 bg-card border-b border-border select-none">{/* Left: app icon + title */}<div className="flex items-center gap-2"><img src="/icon.svg" className="h-5 w-5" alt="" /><span className="text-sm font-medium">Field POS</span></div>{/* Right: window controls — NOT draggable */}<divstyle={{ '--wails-draggable': 'no-drag' } as any}className="flex items-center"><WinBtn onClick={WindowMinimise}><Minus className="h-4 w-4" /></WinBtn><WinBtn onClick={WindowToggleMaximise}><Square className="h-3 w-3" /></WinBtn><WinBtn onClick={Quit} danger><X className="h-4 w-4" /></WinBtn></div></div>)}function WinBtn({ onClick, danger, children }) {return (<buttononClick={onClick}className={`h-10 w-12 flex items-center justify-center hover:bg-muted ${danger ? 'hover:bg-red-500 hover:text-white' : ''}`}>{children}</button>)}
Two key bits:
- The outer div has
--wails-draggable: drag— the whole titlebar can drag the window. - The window-controls cluster has
--wails-draggable: no-drag— buttons are clickable, not drag handles.
The Wails runtime API
WindowMinimise()— minimise to taskbarWindowToggleMaximise()— toggle full vs. windowedWindowFullscreen()— true OS fullscreen (different from maximise)Quit()— exit the appWindowSetTitle(s)— change the title at runtimeWindowCenter()— recenter on screen
All exported from ../wailsjs/runtime/runtime and ready to import.
Why frameless is the right default for line-of-business apps
- Brand consistency. Your app looks the same on every OS. No mismatched native-vs-web fonts in the titlebar.
- More vertical pixels. No 30px OS titlebar eating space. For dashboards, every pixel counts.
- Custom controls. Add a tab bar, search box, or status indicator IN the titlebar. Common pattern in spreadsheet-shaped apps.
Quick check
Try it
Build a working titlebar:
- Set
Frameless: true+ the CSS drag declarations inmain.go. - Add the
TitleBarcomponent above your main layout. - Drag the window by the empty area — confirm it moves.
- Click each window button — Minimise, Maximise, Close — all should work.
What's next
Last lesson of the chapter — going deeper on the window controls: traffic-light styles on Mac, double-click-to-maximize, and the edge cases.
Spot a typo? Have an idea?
Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.
Suggest an improvement on GitHub